Tarjan
有向图
强连通分量
void tarjan(int u) {
dfn[u] = low[u] = ++times;
stk[++top] = u;instk[u] = 1;
for (int i = h[u]; i != -1; i = ne[i]) {
int v = to[i];
if (!dfn[v]) {
tarjan(v);
low[u] = min(low[u], low[v]);
}
else if (instk[u]) low[u] = min(low[u], dfn[v]);
}
if (dfn[u] == low[u]) {
++scc_cnt;
while (1) {
int v = stk[top--];
Size[scc_cnt] ++;
id[v] = scc_cnt;
instk[v] = 0;
if (v == u)break;
}
}
}
2-sat问题
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
const int N = 2e6 + 9;
const int M = 2e6 + 9;
int h[N], ne[M], to[M], idx;
void add(int u, int v) {
ne[idx] = h[u], to[idx] = v, h[u] = idx++;
}
int dfn[N], low[N], times;
int sz[N], id[N], scc_cnt;
int stk[N], instk[N], top;
void tarjan(int u) {
dfn[u] = low[u] = times++;
stk[++top] = u;
instk[u]= 1;
for (int i = h[u]; ~i; i = ne[i]) {
int v = to[i];
if (!dfn[v]) {
tarjan(v);
low[u] = min(low[u], low[v]);
} else if (instk[v]) {
low[u] = min(low[u], dfn[v]);
}
}
if (low[u] == dfn[u]){
scc_cnt++;
while (1) {
int v = stk[top];
top--;
instk[v] = 0;
id[v] = scc_cnt;
sz[scc_cnt] ++;
if (v == u)break;
}
}
}
int main() {
int n, m;
scanf("%d%d", &n, &m);
memset(h, -1, sizeof h);idx = 0;
for (int i = 1; i <= m; i ++) {
int u, x, v, y;
scanf("%d%d%d%d", &u, &x, &v, &y);
add(u + (x^1)*n, v + y * n);//零代表v,1代表v+n,显然
add(v + (y^1)*n, u + x * n);
}
for (int i = 1; i <= 2 * n; i ++) {
if (!dfn[i]) tarjan(i);
}
for (int i = 1; i <= n; i ++) {
if (id[i] == id[i + n]) {//在同一gcc无解
puts("IMPOSSIBLE");
return 0;
}
}
puts("POSSIBLE");
for (int i = 1; i <= n; i ++) {
if (id[i] > id[i + n])printf("1 ");//得到的topo序是反序,所以,越小越靠后,选后面的,又因为i+n是1,i是0
else printf("0 ");
}
}
无向图
求割边(桥),求边双连通分量
void tarjan(int u, int from) {
dfn[u] = low[u] = ++times;
stk[++top] = u;
bool flag = 1;
for (int i = h[u]; i != -1; i = ne[i])
{
int v = to[i];
if ((i^1) == from && flag) {flag = 0;continue;}//重边
if (!dfn[v]) {
tarjan(v, i);
low[u] = min(low[u], low[v]);
if (low[v] > dfn[u]) {
ans++;//i和i^1是桥。
}
} else
low[u] = min(low[u], dfn[v]);
}
if (low[u] == dfn[u]) {
e_dcc++;
while (1) {
id[stk[top]] = e_dcc;
if (stk[top--] == u)break;
}
}
}
求割点(割顶),求点双连通分量
-
首先所求得的是一棵 (dfs) 树,然后在这棵树上,如果子树最低能达到的时间戳,即 (dfn[u] leq low[v]) 都到不了 (u) 上面的祖先,并且这个点 (u eq father) ,意思就是这个点 (u) 不是这个连通块随便找的最开始遍历的点,这样 (u) 是一个割点。
-
如果 (u) 还是满足 (dfn[u] leq low[v]) 并且 (u) 有两个儿子,那么 (u) 是一个割点。
void tarjan(int u, int fa) {
dfn[u] = low[u] = ++times;
stk[++top] = u;
int cnt = 0;
for (int i = h[u]; i != -1; i = ne[i])
{
int v = to[i];
if (!dfn[v]) {
tarjan(v, fa), ++cnt;
low[u] = min(low[u], low[v]);
if (low[v] >= dfn[u]) {
if (u != fa)cut[u] = 1;// 如果所有点到不了u上面去,那么显然 u是割点,
// 可是如果u是这个联通量第一个进来的元素,那么必然没有人会到u上面去
// 所以u != fa
else cut[u] |= (cnt > 1); // 有两个儿子也行,所以是|=
++dcc;
while (1) {
bl[stk[top]].push_back(dcc);//可以bl[dcc].push_back(stk[top]);
//主要是看自己需要啥,就咋写
sz[dcc]++;
if (stk[top--] == v)break;//这里确实有点不好理解,就是说,他这团是把当前这一团外面那一团给算上的,下次补一下图咕咕咕。
}
bl[u].push_back(dcc);
sz[dcc]++;
}
} else
low[u] = min(low[u], dfn[v]);
}
}