题目:https://www.luogu.org/problemnew/show/P3469
分析:
1、用前向星存图,然后按边逆序进行搜索。
2、需要对Tarjan判断割点有条件深刻的认识。
先看代码:if( (u==root&&cnt>1) || (u!=root&&dfn[u]<=low[v])u为割点。
对于下面的图而言:
按边(1,2),(1,3),(1,4)用前向星存图,以结点1为root遍历边,得到的dfs序列是:1->4->3->2
因为1->4时,dfn[1]=low[1]=1,dfn[4]=low[4]=2,将都不满足条件u!=root与dfn[u]<=low[v],从而得不出结点root=1为割点。
当2->3时,dfn[3]=low[3]=3,将得出1是割点,此时连通块{3}的size[3]=1,从而由3连向{2,4}的对数有1*2=2;
当3->2时,dfn[2]=low[2]=4,仍将得出1是割点,此时连通块{2}的size[2]=1,从而由2连向{3,4}的对数有1*2=2;
现在回到连通块{4},由4连向{2,3}的对数有(4-2-1)*2=2。左边第二项的2=size[3}+size[2]。
再看下图:
以1为root。搜索顺序为1->2-3->4->5->6。
由(1,2),(2,3)得不出2是割点,但由(2,4)可以得出2是割点。
同样的,由(2,4)得不出4是割点,但由(4,5),(4,6)都可以得出4是割点。
对于点2而言,ans=size[4]*(n-size[4]-1)+(n-sum-1)*sum+2*(n-1),其中的sum=size[4]。
由上面两个图,要说明的是:
如果点u是割点,则至少有两条边与u相连,其中有一条边得不出u是割点,这在编程是要引起高度重视。
AC代码:
#include<cstdio>
#include<iostream>
#include<cstring>
using namespace std;
const int MaxN=1e5+5,MaxM=10e5+5;
inline int get(){//读入优化
char c=getchar();
int x=0;
while(c<'0'||c>'9')c=getchar();
while(c>='0'&&c<='9'){
x=(x<<3)+(x<<1)+c-'0';
c=getchar();
}
return x;
}
int n,m,root,tot,head[MaxM],to[MaxM],from[MaxM],nex[MaxM];
long long ans[MaxN];
void join(int x,int y){
nex[++tot]=head[x];
head[x]=tot;
to[tot]=y;
from[tot]=x;
}
int num,dfn[MaxN],low[MaxN],size[MaxN],cut[MaxN];
void Tarjan(int u){
dfn[u]=low[u]=++num;
size[u]=1;
int cnt=0,sum=0;
for(int i=head[u];i;i=nex[i]){
int v=to[i];
if(!dfn[v]){
cnt++;
Tarjan(v);
size[u]+=size[v];
low[u]=min(low[u],low[v]);
if( (u==root&&cnt>1)
|| (u!=root&&dfn[u]<=low[v]) ){
cut[u]=1;//标记u是割点
ans[u]+=(long long)(n-size[v]-1)*size[v];
sum+=size[v];//难点在这里!!
}
}
else low[u]=min(low[u],dfn[v]);
}
if(!cut[u])ans[u]=2*(n-1);
if(cut[u])ans[u]+=(long long)(n-sum-1)*sum+2*(n-1); //难点在这里!!
}
int main(){
n=get();m=get();
int x,y;
for(int i=1;i<=m;i++){
x=get();y=get();
join(x,y);join(y,x);
}
root=1;
Tarjan(1);//Tafjan求割点,同时求出答案
for(int i=1;i<=n;i++)
printf("%lld\n",ans[i]);
return 0;
}