bzoj 1123 BLO

题目链接(You can click it.)

题目描述
B城有 n 个城镇,m 条双向道路。
每条道路连结两个不同的城镇,没有重复的道路,所有城镇连通。
把城镇看作节点,把道路看作边,容易发现,整个城市构成了一个无向图。

输入格式
       第一行包含两个整数 nm
       接下来 m 行,每行包含两个整数 ab,表示城镇 ab 之间存在一条道路。

输出格式
       输出共 n 行,每行输出一个整数。
       第 i 行输出的整数表示把与节点 i 关联的所有边去掉以后(不去掉节点 i 本身),无向图有多少个有序点xy),满足 xy 不连通。

       首先明确一点,有序点的含义,也就是说(x, y)和(y, x)不一样。对于一个点,如果它是不是割点,也就是说删除点和关联的边后连通性没有变化,但是题目要求保留点,也就是说这个点孤立无援,和其他所有的点都失去了联系,所以此时有序点个数为 (n-1) * 2。

       如果是割点,一旦删除割点和关联的边,本来连通的部分会分裂为多个互不连通的块。分裂后的连通块可以分为以下三种情况之一。

  1. 节点自身
  2. 搜索树上的以当前节点某字节点为根的子树
  3. 除去以上两种以外剩余的部分节点

       用Size[x]表示以x为根的搜索树的大小,如果当前节点now(是割点)的子搜索树有ab,他们的根分别为ab,那么删除掉now的临边后,有序对有Size[a] × \times × (n-Size[a]) + Size[b] × \times ×(n-Size[b])(这个子连通块内的点与其他不属于这个连通块内的点互为有序对),还有now到其他n-1个点也互为有序对(now孤立了),以及除了这两个部分以外剩余的点与其他点也互为有序对(即(n-Size[a]-Size[b] - 1) × \times ×(Size[a]) + Size[b] + 1)这就是这个割点所有的有序点的个数。

       也许有人有疑问,(x, y)和(y, x)不是不一样的吗?为什么只计算了一次?其实不是计算了一次,而是两次都计算了。例如上述的求解过程中,删除了点now的临边,那么增加的有Size[a] × \times ×(n-Size[a])等等,这个里面就包括了Size[a]和now这个点之间的一次计算,那么这么走下来,整好每个这样的有序点(x, y)和(y, x)都恰恰各被计算了一次。
       比较繁琐的就在于第三种情况,需要用到前面的和,因此可以专门用一个变量sum来记录和,在前面的运算中依次累加,后面直接应用即可。
       总结: 其实就是一个tarjan求割点的问题,稍微复杂了一点。

       Note: 乘法运算可能超出了int范围,推荐使用long long

/**
 * Author : correct
 */
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cstdlib>
#define mem(a ,b) memset(a, b, sizeof a)
#define ll long long
using namespace std;
const int N = 100100, M = 1000100;
int head[N], nex[M], to[M], cnt;
ll Size[N];
ll ans[N];
int dfn[N], low[N], num;
bool cut[N];
int root;
int n, m;
void add(int a, int b){
    
    
	to[++cnt] = b;
	nex[cnt] = head[a];
	head[a] = cnt;
}
void tarjan(int x){
    
    
	dfn[x] = low[x] = ++num;
	Size[x] = 1;
	int f = 0;
	int sum = 0;
	for (int i = head[x]; i; i = nex[i]){
    
    
		int y = to[i];
		if (!dfn[y]){
    
    
			tarjan(y);
			Size[x] += Size[y];
			low[x] = min(low[x], low[y]);
			if (dfn[x] <= low[y]){
    
    
				// x是割点
				f++;
				if (x != root || f > 1)cut[x] = 1;
				//根节点是割点的要求强一点,因为如果仅仅满足dfn[x] <= low[y]
				//若根节点的搜索树只有一个子节点,那么把根删除没有影响,因为它是端点
				//但是如果是中间的点,那它的子节点就一定不能不经过它回到祖先节点,所以它就是割点
				ans[x] += Size[y] * ((ll)n - Size[y]);
				sum += Size[y];
			}
		}
		else low[x] = min(low[x], dfn[y]);
	}
	if (!cut[x]){
    
    //如果不是割点就是(n-1)*2
		ans[x] = (n - 1) * 2;
	}
	else {
    
    //是割点就是前面计算过的加上(n-1)(自己这个点和其他点)  后面是第三种情况下的
		ans[x] += (ll)(n - 1) + (ll)(n - 1 - sum) * (ll)(sum + 1);
	}
}
int main()
{
    
    
	//freopen("in.in", "r", stdin);
	ios::sync_with_stdio(0);
	cin >> n >> m;
	while (m--){
    
    
		int a, b;
		cin >> a >> b;
		add(a, b), add(b, a);
	}
	for (int i = 1; i <= n; i++){
    
    
		if (!dfn[i]){
    
    
			root = i;
			tarjan(i);
		}
	}
	for (int i = 1; i <= n; i++){
    
    
		cout << ans[i] << "\n";
	}
	return 0;
}

猜你喜欢

转载自blog.csdn.net/weixin_43701790/article/details/105243427