- 题面描述
- \(Byteotia\)城市有\(n\)个\(towns\),\(m\)条双向\(roads\). 每条\(road\)连接 两个不同的\(towns\),没有重复的\(road\). 所有\(towns\)连通。
- 输入格式
- 输入\(n\leq 10^5,m\leq 5*10^5\)及\(m\)条边
- 输出格式
- 输出\(n\)个数,代表如果把第\(i\)个点去掉,将有多少对点不能互通。
- 题解
- \(tarjan\)裸题,但是是个不错的理解\(tarjan\)的题
- 当我们想要知道一张图上有没有环的时候,一般有两种方法,
- \(topu\)排序,在排完后如果图中仍存在点没有被排序的话,我们就称这张图上有环。
- \(tarjan\),引入时间戳的概念,对每个点计算他能回到的时间戳最小的点,再利用\(dfs\)搜索树的概念,判断每个点是否在环上面(即能够回到搜索树上比他深度小的点)(这一段看不懂的话,继续往下看)
- 上面我们已经用一句话概括了\(tarjan\)的算法思路。会\(tarjan\)的同学可以出门右转….
- 说了这么多,我们来看看\(tarjan\)到底是个啥?
- 我们先说有向图判环
- 首先我们要先看看\(dfs\)搜索树,当我们使用\(dfs\)的时候我们能够按照节点搜索顺序获得一颗树(每个点都严格入栈一次,出栈一次)。考虑在做\(dfs\)的时候我们为了保证每个点只被搜索一次,我们会对每个点记它是否会被访问\(use_x\)。当我们发现我们下一个要搜索的点\(u\)已经被搜索过(即\(use_u=1\))我们就不再搜索该点,这时候就是存在环了。
- 那你刚刚说的时间戳又是个啥?我们在\(dfs\)对每个点按进栈顺序编号,就是我们所谓的时间戳。注意到我们在做\(dfs\)(深度优先搜索)因此先被搜索的点先被打上时间戳,后搜索的点的时间戳必然比前面大。
- 那有没有可能本来有环但搜索环的时候没有碰到已经打过时间戳的节点?不可能,想要进环就必须先搜索环上其中一个点。
- 这样我们就能根据当前打的时间戳判断图中是否有环。
- 对于一个点\(u\),枚举它的儿子\(v\),如果\(v\)尚未被打上时间戳,就继续\(dfs\ v\),不然就看\(u\)的时间戳和\(v\)的时间戳那个大,如果\(v\)的时间戳小于\(u\)的时间戳那么我们就可以声称我们找到一个环
- 有了\(dfs\)搜索树和时间戳的铺垫,我们再来看怎么做这题。
- 现在这张图是一张无向图,我们引入一个新的概念
- \(low_u\)表示\(u\)或\(u\)的子树能够追溯到的最早的栈中节点的时间戳
- 有了这个,我们再来看题目要求的
把该点去掉后不能互相连通的节点对数
。那么就相当于要求\(low_u\)能回到的最早的栈中节点是它在\(dfs\)搜索树上的父亲。(想想为啥)
- 算法流程就是:对于一个点\(u\),枚举它的儿子\(v\),如果\(v\)还没有时间戳就继续搜索并将\(v\)的\(low\)合并,即\(low_u=min(low_u,low_v)\)。如果\(v\)已经有了时间戳,我们直接将\(v\)的时间戳合并\(low_u=min(low_u,dfn_v)\)。
- 这里还要提一句,在做有向图的强连通分量的时候对于已经有时间戳的\(v\)合并\(low_v\)也可以,但在做无向图双连通分量的时候就不行了,只能用\(dfn_v\),因为如果用了\(low_v\)那么他就一定能够回到比\(v\)还前边的点。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;
const int MAXN=1e5+5;
const int MAXM=1e6+6;
int edge,head[MAXN],nex[MAXM],tail[MAXM];
int dfn[MAXN],low[MAXN];
int n,m,cnt;
int sz[MAXN];
ll ans[MAXN];
void add(int u,int v){
edge++,nex[edge]=head[u],head[u]=edge,tail[edge]=v;
}
void tarjan(int u){
sz[u]=1; dfn[u]=low[u]=++cnt;
int t=0;
for (int e=head[u];e;e=nex[e]){
int v=tail[e];
if (!dfn[v]){
tarjan(v);
sz[u]+=sz[v];
low[u]=min(low[u],low[v]);
if (dfn[u]<=low[v]){
ans[u]+=(ll)t*sz[v];
t+=sz[v];
}
}
else low[u]=min(low[u],dfn[v]);
}
/*if (low[u]==dfn[u])*/ ans[u]+=(ll)t*(n-t-1);
}
int main(){
scanf("%d%d",&n,&m);
for (int i=1;i<=m;i++){
int u,v; scanf("%d%d",&u,&v);
add(u,v); add(v,u);
}
tarjan(1);
for (int i=1;i<=n;i++) printf("%lld\n",(ans[i]+n-1)*2);
return 0;
}