题解 LuoguP3469 [POI2008]BLO-Blockade

题目传送门

题意:有nn个点、mm条边的无向图,对任意1in1\le i\le n,求出将第ii个点和与其关联的边删去时,有多少个有序点对(u,v)(u,v)满足uuvv不连通。

显然,这道题需要用到割点。

先考虑ii不是割点的情况。因为ii不是割点,所以把ii和与其相关联的边去掉时,其他n1n-1个点依然是连通的。因此答案为2(n1)2(n-1)。(注意题目中说的是有序点对

然后来考虑ii是割点的情况。

很显然,若ii是割点,删去ii和与其相关联的边后,原图会变成若干个连通块。那怎么求这些连通块的大小呢?

我们思考一下:tarjan算法是如何判定割点的?

答案是:在搜索树上,如果ii不是根节点,则如果有任意sonison_i满足dfn[i]low[soni]dfn[i]\le low[son_i]ii即为割点。当dfn[i]low[soni]dfn[i]\le low[son_i]时,sonison_i除了往上走树边到达ii以外不能到达ii子树外的地方,也就是说删除(i,soni)(i,son_i)这条边后,图会分为sonison_i的子树与其它这两个部分。所以ii就是割点。

所以这道题的答案就很显而易见了:对任意满足割点判定式dfn[i]low[soni]dfn[i]\le low[son_i]的儿子sonison_i,在删去ii和与其相关联的边后,sonison_i的子树会变成图中的一个单独的连通块。因此我们只需要在tarjan找割点的同时统计一下这些子树的大小加进答案里即可。

别忘了还有一个连通块是除了ii子树以外的所有点。

Code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
#include <bits/stdc++.h>
#define int long long
#define rep(i,a,b) for(int i=(a);i<=(b);++i)
#define per(i,a,b) for(int i=(a);i>=(b);--i)
using namespace std;
const int N=1e5+5,M=1e6+5;
int target[M],last[N],pre[M],tot=0;
int n,m;
int ans[N];
int dfn[N],low[N],sz[N],cnt=0;
void add(int u,int v) {
target[++tot]=v;
pre[tot]=last[u];
last[u]=tot;
}
int tarjan(int u){
sz[u]=1;
dfn[u]=low[u]=++cnt;
int flg=0,sum=0;
bool iscut=false;
for(int ptr=last[u];ptr;ptr=pre[ptr]) {
int v=target[ptr];
if(!dfn[v]) {
sz[u]+=tarjan(v);
low[u]=min(low[u],low[v]);
if(low[v]>=dfn[u]) {
++flg;
ans[u]+=sz[v]*(n-sz[v]);
sum+=sz[v];
if(u!=1||flg>1) iscut=true;
}
} else low[u]=min(low[u],dfn[v]);
}
if(iscut) ans[u]+=(sum+1)*(n-sum-1)+(n-1);
else ans[u]=2*(n-1);
return sz[u];
}
signed main(){
scanf("%lld%lld",&n,&m);
rep(i,1,m) {
int u,v;
scanf("%lld%lld",&u,&v);
add(u,v);add(v,u);
}
tarjan(1);
rep(i,1,n) printf("%lld\n",ans[i]);
return 0;
}