BZOJ1123 BLO : Tarjan割点+乘法原理+dfs
Description
给定一张无向图,求每个点被封锁之后有多少个有序点对(x,y)(x!=y,1<=x,y<=n)满足x无法到达y
Input
第1行:N, M (1<=N<=100000, 1<=M<=500000)
第2~M+1行 X Y 表示X与Y中有一条边。
Output
共N行,每行一个正整数代表如果去掉第i个点有多少个不能到大的点对。
Sample Input
5 5
1 2
2 3
1 3
3 4
4 5
Sample Output
8
8
16
14
8
HINT
题解
这一题中蕴含的技巧就是求割点时计算其将图分成了多少个大小为多少的连通块。
因为答案就是这些连通块大小互相乘的和。
关键在于,再求割点时维护一个vis[i]代表搜索树中这个子树的大小。
因为一个割点将图分成的连通块是其下面的所有子树(互不相连)与这个点上面的所有点。tmp表示这个点的子树之前的同父亲子树的和。为什么这么算可以得到答案,可以自己推一推。
不要忘记最后答案要乘2。
#include <cstdio>
#include <iostream>
#include <cmath>
#include <stack>
#include <algorithm>
#include <cstring>
#include <climits>
#define MAXN 150000+10
#define MAXM 700000+10
#define LL long long
using namespace std;
int head[MAXN],num,n,m;
int dfn[MAXN],low[MAXN],vis[MAXN],dfnum,root,son;
LL ans[MAXN];
struct Edge{
int from,to,next;
}edge[MAXM*2];
void add(int from,int to)
{
edge[++num].next=head[from];
edge[num].from=from;
edge[num].to=to;
head[from]=num;
}
void tarjan(int x,int fa)
{
LL cnt=0;
dfn[x]=low[x]=++dfnum;vis[x]=1;
for(int i=head[x];i;i=edge[i].next)
{
if(edge[i].to==fa) continue;
if(!dfn[edge[i].to])
{
tarjan(edge[i].to,x);
vis[x]+=vis[edge[i].to];//记大小
low[x]=min(low[x],low[edge[i].to]);
if(low[edge[i].to]>=dfn[x])
{
ans[x]+=1LL*cnt*vis[edge[i].to];//加上到edge[i].to这颗子树的方案
cnt+=1LL*vis[edge[i].to];
}
}else if(vis[edge[i].to]) low[x]=min(low[x],dfn[edge[i].to]);
}
ans[x]+=1LL*cnt*(n-cnt-1);//加上x上面的点的方案
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++)
{
int x,y;
scanf("%d%d",&x,&y);
add(x,y);add(y,x);
}
tarjan(1,0);
for(int i=1;i<=n;i++)
printf("%lld\n",1LL*(ans[i]+n-1)*2);
return 0;
}