2019牛客暑期多校训练营 (第九场) E All men are brothers 【并查集+思维】

题目描述

Amy asks Mr. B  problem E. Please help Mr. B to solve the following problem.

There are n people, who don't know each other at the beginning.

There are m turns. In each turn, 2 of them will make friends with each other.

The friend relation is mutual and transitive.

If A is a friend of B, then B is also a friend of A.

For example, if A is a friend of B, B is a friend of C, then A and C are friends.

At the beginning and after each turn, please calculate the number of ways to select four people from, such that any two of these four are not friends.

输入描述 

The first line contains two integers, n and m (n <= 100000, m <= 200000), which are the number of people, and the number of turns.

In the following m lines, the i-th line contains two integers x and y ( 1 <= x <= n, 1 <= y <= n, x ≠ y), which means the x-th person and the y-th person make friends in the i-th turn.

The x-th person and y-th person might make friends in several turns.

输出描述 

Output m+1 lines, each line contains an integer, which is the number of quadruples.

Output at the beginning and after each turn, so there are m+1 lines.

示例输入1 

6 6
1 2
3 4
4 5
3 5
3 6
2 4

示例输出1 

15
9
4
0
0
0
0

示例输入2 

100000 0

示例输出2 

4166416671249975000

说明 

Don't use int.

题目大意:

有n个人,进行m次操作。每次操作使得两个人成为朋友,朋友的关系是可以传递的,即当两个人成为朋友后,与这两个人是朋友的所有人都成为了朋友。

计算执行每次操作后,选择四个人两两都不是朋友的不同方案的数目。

分析:

由题目中的操作以及关系的传递性,可以想到用并查集维护,同时维护每个集合中元素的个数。

初始状态下,方案数为 C_n^4 ,我们考虑每次操作减去的数。

每次减去的方案是:在当前两个集合中各选一个,此外在其他集合中选择两个集合各选一个。设合并的两个集合元素个数为a、b,则每次减去的数 = a * b * (在其他集合中选出2个元素,且这两个元素不在同一集合的方案数)。

求在其他集合中选出2个元素,且这两个元素不在同一集合的方案数,可以先求任选2个的方案数,再减去来自同一个集合的方案数。

因此,我们可以在进行每个操作时维护一个 tmp 值,tmp=\sum_{i=1}^{k}C_{size[i]}^2,(size[ i ]中是每个集合的元素数),记录当前状态下选择两个元素在同一集合中的方案数。

具体解释见代码。

#include <bits/stdc++.h>

using namespace std;

typedef unsigned long long ll;

const ll maxn=100005;

ll fa[maxn],size[maxn];

//并查集的Get操作 
ll get(ll x){
	if(fa[x]==x)  return x;
	return fa[x]=get(fa[x]);
}

//并查集的Merge操作
void merge(ll x,ll y){
	ll fx=get(x),fy=get(y); 
	fa[fy]=fx;
	size[fx]=size[fx]+size[fy];
}

//计算每个集合中选择两个元素的方案数 
ll getCi2(ll num){
	if(num<2)  return 0;
	else  return num*(num-1)/2;
}

int main(){
	ll n,m;
	scanf("%lld%lld",&n,&m);
	ll x,y;
	//初始化 
	for(ll i=1;i<=n;i++){
		fa[i]=i;
		size[i]=1;
	}
	//n<4要单独判断 
	if(n<4){
		for(ll i=1;i<=(m+1);i++){
			printf("0\n"); 
		}
		return 0;
	}
	ll ans=n*(n-1)/2*(n-2)/3*(n-3)/4;//这里要注意不能按下行的方式写,否则中间结果会溢出unsigned long long 
//	ll ans=(n*(n-1)*(n-2)*(n-3))/(24);
	printf("%lld\n",ans); 
	ll tmp=0;//维护的tmp值 
	for(ll i=1;i<=m;i++){
		scanf("%lld%lld",&x,&y);
		ll fx=get(x),fy=get(y);
		if(fx==fy){//若两个人已经是朋友,不做操作 
			printf("%lld\n",ans);
			continue;
		}
		//减去当前两个集合下的方案数 
		tmp-=getCi2(size[fx]);
		tmp-=getCi2(size[fy]);
		ans-=size[fx]*size[fy]*((n-size[fx]-size[fy])*(n-size[fx]-size[fy]-1)/2-tmp);
		printf("%lld\n",ans);
		tmp+=getCi2(size[fx]+size[fy]);//更新tmp值 
		merge(x,y);//合并两个集合 
	}
	return 0;
} 

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值