[POI2008]BLO-Blockade

题面

[POI2008]BLO-Blockade

题解

深度优先遍历这幅图。
\(siz[x]\) 表示在搜索树中,以 \(x\) 为根的子树的大小。
注意不连通的关系是双向的,所以\((x,y),(y,x)\)算两次。
对于当前点 \(x\),有两种情况:

  • \(x\) 是割点
    那么删去 \(x\) 之后会有如下的点不连通:
    • \(x\) 在搜索树上的子树中所有的节点与其它的点:

    \[\sum_{k = 1}^{t}siz[k] * (n - siz[k])(t为子节点总数) \]

    • \(x\) 与其余的 \(n - 1\) 个节点。
    • \(x\) 的祖先节点和 \(x\) 的祖先节点的兄弟姐妹节点,与 \(x\)以及它的子树:

    \[(n - 1 -\sum_{k = 1}^{t}siz[k]) \times(1 + \sum_{k = 1}^{t}siz[k] ) \]

  • \(x\) 不是割点
    那么删去与 \(x\) 相连的边之后不会影响除 \(x\) 以外的节点的连通性。所以只有 \(x\) 与其它节点不连通。

    \[ans = 2 \times (n - 1) \]

于是我们在 \(tarjan\) 求割点的同时求出以上的值,就能得到答案了。

代码如下

#include<cstdio>
#include<iostream>
using std::min;
const int N = 1e5 + 5,M = 5e5 + 5;
struct edge {
	int next,to;
}a[M << 1];
int head[N],n,m,a_size = 1,root;
inline void add(int u,int v) {
	a[++a_size] = (edge){head[u],v};
	head[u] = a_size;
	a[++a_size] = (edge){head[v],u};
	head[v] = a_size;
}
int dfn[N],low[N],siz[N],num = 0;
long long ans[N]; bool cut[N];
void tarjan(int x) {
	dfn[x] = low[x] = ++num;
	int flag = 0,sum = 0; siz[x] = 1;
	for(int i = head[x]; i; i = a[i].next) {
		int y = a[i].to;
		if(!dfn[y]) {
			tarjan(y); siz[x] += siz[y];
			low[x] = min(low[x],low[y]);
			if(low[y] >= dfn[x]) {
				flag++; sum += siz[y]; 
				ans[x] += 1ll * siz[y] * (n - siz[y]);
				if(x != root || flag > 1) cut[x] = true;
			}
		}
		else low[x] = min(low[x],dfn[y]);
	}
	if(cut[x]) ans[x] += 1ll * (n - 1 - sum) * (1 + sum) + n - 1;
	else ans[x] = (n - 1) << 1;
}
inline int read() {
	int x = 0,flag = 1;
	char ch = getchar();
	while(ch < '0' || ch > '9'){if(ch == '-')flag = -1;ch = getchar();}
	while(ch >='0' && ch <='9'){x = (x << 3) + (x << 1) + ch - 48;ch = getchar();}
	return x * flag;
}
int main() {
	n = read(),m = read();
	for(int i = 1; i <= m; i++) {
		int u = read(),v = read();
		add(u,v);
	}
	for(int i = 1; i <= n; i++) {
		if(dfn[i]) continue;
		root = i; tarjan(root);
	}
	for(int i = 1; i <= n; i++) printf("%lld\n",ans[i]);
	return 0;
}
posted @ 2021-05-29 20:06  init-神眷の樱花  阅读(81)  评论(0)    收藏  举报