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;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值