对于本题。每个点有两种,割点和正常点
由于割点确实没有太认真学过。。感觉自己像发现新大陆一样。。。。
正常的点一旦被去掉了会少2*(n-1)次访问,整个图依旧是一个完整连通图,而割点去掉后,整个图就会被划分成几个小的连通子图
那么对于割点被消掉后,不但少了 2*(n-1)个访问,划分成了多个子图,子图之间的访问次数也会减少,由于关系是相互的,所以对于割点消除后,一共减少 pre * son * 2个点(pre代表 父节点内全部点, son 代表子树内全部点)
当然 假设这个割点一旦存在 2个甚至多个子树,我们还要减去重复计数的部分。
可以想一下。。。举个例子:
1-2 2-3 2-4 这种情况。
减去重复技术,对于割点,我们在进行tarjan的时候,要设置父节点,避免原路返回,之后如果 low[ t ] >= dfn[ x ] 说明 x->t点后,t除了原路返回之外,回不到比 x 这个点更靠前的点了,说明 x 就是一个割点了。
以下是 AC 代码。。
割点模板题 可以参考 P3388
#include<bits/stdc++.h>
using namespace std;
const int maxn = 1e5+5;
#define ll long long int
struct edge
{
ll nxt,to;
}ed[maxn*30];
ll head[30*maxn],cnt;
ll dfn[maxn],low[maxn],tot,Bcnt;
ll ans[maxn],sz[maxn];
ll n,m;
inline void add(int a,int b)
{
ed[++cnt].to=b;
ed[cnt].nxt=head[a];
head[a]=cnt;
}
inline ll read()
{
int X=0,w=0; char ch=0;
while(!isdigit(ch)) {w|=ch=='-';ch=getchar();}
while(isdigit(ch)) X=(X<<3)+(X<<1)+(ch^48),ch=getchar();
return w?-X:X;
}
void tarjan(int x, int f)
{
int size = 1, ua = 0;
dfn[x] = low[x] = ++tot;
for(int i = head[x]; i ; i=ed[i].nxt)
{
ll t = ed[i].to;
if(t == f) continue;
if(!dfn[t])
{
tarjan(t, x);
size += sz[t];
low[x] = min(low[x], low[t]);
if(low[t] >= dfn[x])
{
ua += sz[t];
ans[x] += 2 * sz[t] * (n - ua - 1);
}
}
else low[x] = min(low[x], dfn[t]);
}
sz[x] = size;
ans[x] += 2 * (n - 1);
}
int main()
{
n=read(); m=read();
int a,b;
for(int i = 1; i <= m; i++)
{
a=read(),b=read();
add(a, b); add(b, a);
}
tarjan(1, 0);
for(int i = 1; i <= n; i++)
printf("%lld\n", ans[i]);
return 0;
}