参考博客:
https://blog.csdn.net/izumi_hanako/article/details/78082544
我以前的博客:
https://blog.csdn.net/Cold_Chair/article/details/79918157
1.割点
对于一个无向连通图,如果有一个点(x),删掉它之后剩下的点就不连通了,它就是割点。
随便以一个点(st)开始做tarjan,对于非起点的点(x eq st),如果有一个儿子(yin son[x])满足(low[y]>=dfn[x]),即(y)上不去了,那么(x)就是割点。
对于起点(st),如果它在tarjan时有两个及以上的子树,它就是割点。
2. 桥边
对于一个无向连通图,如果有一个边((x,y)),删掉它之后就不连通了,它就是桥边。
tarjan时有一条边:(x->y),若(low[y]>dfn[x]),则((x,y))是桥边。
3. 有向图缩强联通分量
dfs树上有三种边:
- 树边
- 返祖边
- 横插边
在求(low)时,横插边忽略,只考虑树边和返祖边,所以需要记录哪些点在栈里。
做完一个点(x)的子树后,若(low[x] ge dfn[x]),则栈顶到(x)的点可以缩成一个强联通分量。
4. 无向图缩边双
无向图没有横插边。
同有向图缩强联通分量。
注意遍历树时不能父亲过来的反向边,所以dfs时多记一下从哪条边来的。
5. 无向图缩点双(圆方树)
需要理解找桥边的过程先。
如果有一个树边(x->y),满足(low[y] ge dfn[x]),那么目前栈顶到(y)的点和(x)就形成了一个点双。
注意同样不能走父边。
代码:
1.https://www.luogu.com.cn/problem/P3388
#include<bits/stdc++.h>
#define fo(i, x, y) for(int i = x, _b = y; i <= _b; i ++)
#define ff(i, x, y) for(int i = x, _b = y; i < _b; i ++)
#define fd(i, x, y) for(int i = x, _b = y; i >= _b; i --)
#define ll long long
#define pp printf
#define hh pp("
")
using namespace std;
const int N = 2e5 + 5;
int n, m, x, y;
int fi[N], to[N * 2], nt[N * 2], tot = 1;
void link(int x, int y) {
nt[++ tot] = fi[x], to[tot] = y, fi[x] = tot;
}
int low[N], dfn[N], dfn0;
int son[N], ans[N];
void dg(int x, int la) {
low[x] = dfn[x] = ++ dfn0;
for(int i = fi[x]; i; i = nt[i]) if(i != la) {
int y = to[i];
if(!dfn[y]) {
dg(y, i ^ 1);
low[x] = min(low[x], low[y]);
son[x] ++;
if(low[y] >= dfn[x]) ans[x] = 1;
} else low[x] = min(low[x], dfn[y]);
}
if(la == 0 && son[x] <= 1) ans[x] = 0;
}
int main() {
scanf("%d %d", &n, &m);
fo(i, 1, m) {
scanf("%d %d", &x, &y);
link(x, y); link(y, x);
}
fo(i, 1, n) if(!dfn[i])
dg(i, 0);
int s0 = 0;
fo(i, 1, n) s0 += ans[i];
pp("%d
", s0);
fo(i, 1, n) if(ans[i]) pp("%d ", i);
}
3.https://www.luogu.com.cn/problem/P3387
#include<bits/stdc++.h>
#define fo(i, x, y) for(int i = x, _b = y; i <= _b; i ++)
#define ff(i, x, y) for(int i = x, _b = y; i < _b; i ++)
#define fd(i, x, y) for(int i = x, _b = y; i >= _b; i --)
#define ll long long
#define pp printf
#define hh pp("
")
using namespace std;
const int N = 1e5 + 5;
int n, m, a[N], x, y;
int fi[N], nt[N], to[N], tot;
void link(int x, int y) {
nt[++ tot] = fi[x], to[tot] = y, fi[x] = tot;
}
int low[N], dfn[N], dfn0;
int bz[N], z[N], z0;
int id[N], id0, v[N];
vector<int> p[N], e[N];
#define pb push_back
#define si size()
void dg(int x) {
low[x] = dfn[x] = ++ dfn0;
z[++ z0] = x; bz[x] = z0;
for(int i = fi[x]; i; i = nt[i]) {
int y = to[i];
if(!dfn[y]) {
dg(y);
low[x] = min(low[x], low[y]);
} else if(bz[y]) low[x] = min(low[x], dfn[y]);
}
if(low[x] >= dfn[x]) {
id0 ++;
do {
bz[z[z0]] = 0;
id[z[z0]] = id0;
p[id0].pb(z[z0]);
v[id0] += a[z[z0]];
} while(z[z0 --] != x);
}
}
int r[N], d[N], d0;
int f[N];
int main() {
scanf("%d %d", &n, &m);
fo(i, 1, n) scanf("%d", &a[i]);
fo(i, 1, m) {
scanf("%d %d", &x, &y);
link(x, y);
}
fo(i, 1, n) if(!dfn[i])
dg(i);
fo(i, 1, id0) {
ff(_x, 0, p[i].si) {
int x = p[i][_x];
for(int j = fi[x]; j; j = nt[j]) {
int y = to[j];
if(i != id[y]) {
e[i].pb(id[y]);
}
}
}
}
fo(i, 1, id0) {
ff(_j, 0, e[i].si) {
int j = e[i][_j];
r[j] ++;
}
}
fo(i, 1, n) if(!r[i]) {
d[++ d0] = i;
}
for(int i = 1; i <= d0; i ++) {
int x = d[i];
ff(_j, 0, e[x].si) {
int y = e[x][_j];
if(!(-- r[y])) d[++ d0] = y;
}
}
int ans = 0;
fd(i, d0, 1) {
int x = d[i];
ff(_j, 0, e[x].si) {
int y = e[x][_j];
f[x] = max(f[x], f[y]);
}
f[x] += v[x];
ans = max(ans, f[x]);
}
pp("%d
", ans);
}
void tar(int x, int la) {
d[++ d[0]] = x; low[x] = dfn[x] = ++ td;
for(int i = final[x]; i; i = next[i]) if(i != (la ^ 1)){
int y = to[i];
if(!dfn[y]) tar(y, i), low[x] = min(low[x], low[y]); else
low[x] = min(low[x], dfn[y]);
}
if(dfn[x] == low[x]) {
tz ++;
do to[d[d[0]]] = tz; while(d[d[0] --] != x);
}
}
void tar(int x, int la) {
dfn[x] = low[x] = ++ tt;
z[++ z[0]] = x;
for(int i = e.final[x]; i; i = e.next[i]) {
int y = e.to[i];
if(!dfn[y]) {
tar(y, i);
low[x] = min(low[x], low[y]);
if(low[y] >= dfn[x]) {
td ++;
while(z[z[0]] != y) e2.link(z[z[0] --], td);
e2.link(z[z[0] --], td); e2.link(x, td);
}
} else if(i != (la ^ 1)) low[x] = min(low[x], dfn[y]);
}
}