对tarjan的一些理解
之前做tarjan的题,我一直没有搞清楚有向图和无向图中,代码的不同,今天下午向虎哥和zxk讨论了快一个小时,现在终于清楚些了。
最基本的一些东西
有向图
我们需要求的是强连通分量,在有向图中,有四种边。
一种边为树枝边,从根节点遍历,每个节点第一次被访问到,即边(x,y)是从x到y是对y的第一次访问。这些边为树边,绿色表示
一种边为前向边,边(x,y)可以为表示x是y的祖先。蓝色表示。这种边对求scc没有影响,因为搜索树本来就存在从x到y的路径
一种边为后向边,边(x,y)可以表示y是x的祖先。黄色表示。这种边有用,这条边可以和搜索树上的从y到x的路径构成一个scc
一种边为横叉边,可以理解为连接到两个兄弟、堂兄弟等之间,二者不像前三个有祖先后裔的关系。红色表示。如果存在一条边可以从y到x的祖先,那么可以构成一个scc,否则这条边没用。
由于横叉边的存在,我们必须要判断当前点是否在栈内,如果在栈里说明v是u的父亲或祖先,不在栈里说明u和v属于不同分支。有可能我们从u通过横叉边访问到v,但是并不一定从v访问到u的祖先,也就是说虽然u和v通过一条边相连,但它们并不属于一个scc。如果是无向图,那肯定可以从v访问到u。
void Tarjan(int rt){
dfn[rt] = low[rt] = ++dfn_cnt;
ins[rt] = 1;
for (int i = head[rt]; i; i = edge[i].next){
int v = edge[i].to;
if (!dfn[v]){
Tarjan(v);
low[rt] = min(low[rt], low[v]);
}
else if(ins[v]){//注意这里需要判断是否在栈内
low[rt] = min(low[rt], dfn[v]);
}
}
if (dfn[rt] == low[rt]){
tot++;
while (1){
int cur = stk[top--];
belong[cur] = tot;
ins[cur] = 0;//记得取消标记
scc[tot].push_back(cur);
if (cur == rt) break;
}
}
}
无向图
我们需要求的是割点,点双;桥,边双。
有向图有横叉边,只能从u访问到不同分支的v,但不能从v访问到u
无向图没有横叉边,如果u能访问到v,那v肯定能访问到u
所以不需要判断是不是在栈内
void tarjan(int rt){
dfn[rt] = low[rt] = ++dfn_cnt;
int ch = 0, sum = 0;
for (int i = head[rt]; i; i = edge[i].next){
int v = edge[i].to;
if (!dfn[v]){
Tarjan(v);
low[rt] = min(low[rt], low[v]);
ch++;
if ((rt == root && ch >= 2) || (rt != root && low[v] >= dfn[u]))
cut[rt] = 1;
}
else low[rt] = min(low[rt], dfn[v]);//不需要判断是否在栈内
}
}
一道题
题目描述
Byteotia城市有n个 towns m条双向roads. 每条 road 连接 两个不同的 towns ,没有重复的road. 所有towns连通。
输入格式
输入n<=100000 m<=500000及m条边
输出格式
输出n个数,代表如果把第i个点去掉,将有多少对点不能互通。
样例输入
5 5
1 2
2 3
1 3
3 4
4 5
样例输出
8
8
16
14
8
对于一个非割点,删去会多出来2 * (n-1)对
对于一个割点,删去会有三部分,一是该点本身,二是该点的子树中可能会分成几部分,三是除去以该点为子树的所有节点后剩余部分,每部分相乘再相加即可。
通过这个题我发现对于一个割点u,并不是把它删去后,它所有子节点v都会满足dfn[u]<=low[v],只是一部分子节点会分开,还有一部分仍然属于一个点双。
#include<bits/stdc++.h>
using namespace std;
const int maxn = 1e5 + 5;
const int maxm = 5e5 + 5;
typedef long long ll;
struct Edge{
int to, next;
}edge[maxm << 1];
int n, m, cnt, tot, dfn_cnt, head[maxn], dfn[maxn], low[maxn], ins[maxn], belong[maxn];
int num[maxn];
int in[maxn];
ll ans[maxn];
ll size[maxn], cut[maxn], root;
void Add(int u, int v){
edge[++cnt].to = v;
edge[cnt].next = head[u];
head[u] = cnt;
}
void Tarjan(int rt){
dfn[rt] = low[rt] = ++dfn_cnt;
size[rt] = 1;
int ch = 0, sum = 0;
for (int i = head[rt]; i; i = edge[i].next){
int v = edge[i].to;
if (!dfn[v]){
Tarjan(v);
size[rt] += size[v];
low[rt] = min(low[rt], low[v]);
if (low[v] >= dfn[rt]){
ch++;
ans[rt] += (ll)size[v] * (n - size[v]);
sum += size[v];
if (rt != -1 || ch > 1) cut[rt] = 1;
}
}
else low[rt] = min(low[rt], dfn[v]);
}
if (cut[rt]) ans[rt] += n - 1 + (ll)(n - sum - 1) * (sum + 1);
else ans[rt] += (n - 1) * 2;
}
int main(){
scanf("%d%d", &n, &m);
for (int i = 1; i <= m; i++){
int u, v;
scanf("%d%d", &u, &v);
if (u == v) continue;
Add(u, v);
Add(v, u);
}
Tarjan(1);
for (int i = 1; i <= n; i++)
cout << ans[i] << endl;
}