【AtCoder】【组合数学】【模型转换】Colorful Balls(AGC012)

题意:

有n个球,每个球有两个值,一个是颜色,另一个是重量。可以进行如下的操作任意次:
1.选择两个颜色相同的球,如果这两个球的重量之和小于等于X,就交换这两个球;
2.选择两个颜色不同的球,如果这两个球的重量之和小于等于Y,就交换这两个球。
问最后能够得到的本质不同的颜色的序列有多少个。

数据范围:

1<=n,color<=10^5
其余值均<=10^5

思路:

假如说X=INF,Y=INF,那么这道题就是一道重排的题目了。
现在有了X和Y的限制,那么就可以考虑到底哪些球是“自由”的,也就是可以互相随意变更位置。因为如果a能够和b交换位置,b能够和c交换位置,那么a就能够和c交换位置,也就是说交换是具有传递性的,那么我们就只需要考虑所有的球能否和在X和Y的交换含义下的重量最小的球进行交换,就可以了。然后我们把两个点可以交换看成一条边,最后与重量最小的球相连的点就是所有的可以自由移动的点了。

首先考虑X。
那么这个时候就是看同种颜色的交换能否进行。加入说当前的球为i,颜色为c[i],重量为w[i],那么再假设颜色为c[i]的最小重量的球为minpos[c[i]]。如果说 w [ m i n p o s [ c [ i ] ] ] + w [ i ] < = X w[minpos[c[i]]]+w[i]<=X w[minpos[c[i]]]+w[i]<=X,那么就说明当前的第i个球是能够进行同种颜色的转移的。

然后考虑Y。
这个时候就是看颜色不同的球能否互相转移。假设全局重量最小的球颜色mnp1,与mnp1颜色不同的最小球为mnp2,对于一个球来说,只需要考虑w[i]+w[mnp1]<=Y或者是w[i]+w[mnp2]<=Y就可以了。因为假设两个球能够和其中的一个同时连边,显然就可以达成目标了;假设两个球只能够和两个球分别连边(颜色所迫),有因为这两个球的重量是分别是>=w[mnp1],>=w[mnp2]的,所以说w[mnp1]和w[mnp2]也一定能够连起来,就可以让这两个球联通了。

最后从全局最小的球mnp1出发,跑一遍DFS,将所有的与mnp1联通的球都打上vis标记,表示这些球都是能够随意换位置的自由球。然后对于剩余的没有打上vis标记的点来说,要么是根本就没有连边,要么就是与自己颜色的最小球连了边,然而后者是没有用的,因为是求颜色序列的方案数。然后相当于就是那些球的位置就已经固定死了,假如自由球的序列已经确定了,那么这些球的位置也相对固定了。

然后对于自由球来说,就是一个重排的问题了。可以用重排的公式求出,也可以直接用 ∑ C t o t c n t c o l o r i \sum C_{tot}^{cnt_{color_i}} Ctotcntcolori求出。那么问题得到解决。

代码:

#include
#include
#include
#include
#define MAXN 200000
#define MO 1000000007
#define INF 0x3FFFFFFF
using namespace std;
int n,X,Y;
int c[MAXN+5],w[MAXN+5];
int fact[MAXN+5],inv[MAXN+5];
int minpos[MAXN+5],minval[MAXN+5];//代表的是某种颜色的最小重量以及球的编号
int cnt[MAXN+5];
bool vis[MAXN+5];
vector<int> G[MAXN+5];
int PowMod(int a,int b)
{
	int ret=1;
	while(b)
	{
		if(b&1)
			ret=1LL*ret*a%MO;
		a=1LL*a*a%MO;
		b>>=1;
	}
	return ret;
}
void Init()
{
	for(int i=0;i<=MAXN+3;i++)
		minval[i]=INF;
	fact[0]=1;
	for(int i=1;i<=MAXN;i++)
		fact[i]=1LL*fact[i-1]*i%MO;
	inv[MAXN]=PowMod(fact[MAXN],MO-2);
	for(int i=MAXN-1;i>=0;i--)
		inv[i]=1LL*inv[i+1]*(1LL*i+1LL)%MO;
}
void DFS(int u)//使用DFS求出所有的与mnp1联通的球
{
	vis[u]=true;
	for(int i=0;i<(int)G[u].size();i++)
	{
		int v=G[u][i];
		if(vis[v]==false)
			DFS(v);
	}
}
int C(int a,int b)
{
	return 1LL*fact[a]*inv[b]%MO*inv[a-b]%MO;
}
int main()
{
	Init();
	scanf("%d %d %d",&n,&X,&Y);
	for(int i=1;i<=n;i++)
	{
		scanf("%d %d",&c[i],&w[i]);
		if(minval[c[i]]>w[i])
		{
			minval[c[i]]=w[i];
			minpos[c[i]]=i;
		}
	}
	for(int i=1;i<=n;i++)
		if(i!=minpos[c[i]]&&w[i]+minval[c[i]]<=X)
			G[i].push_back(minpos[c[i]]),G[minpos[c[i]]].push_back(i);//同种颜色的建图
	int mn1=INF,mn2=INF;
	int mnp1,mnp2;
	//先找最小值 
	for(int i=1;i<=n;i++)
		if(w[i]<mn1)
			mn1=w[i],mnp1=i;
	//再找与最小值颜色不同的最小值 
	for(int i=1;i<=n;i++)
		if(w[i]<mn2&&c[i]!=c[mnp1])
			mn2=w[i],mnp2=i;
	for(int i=1;i<=n;i++)
	{
		if(i==mnp1)//全局最小球当然不需要和自己连边
			continue;
		if(c[i]!=c[mnp1]&&w[i]+w[mnp1]<=Y)//与最小球颜色不同
			G[i].push_back(mnp1),G[mnp1].push_back(i);
		if(mn2!=INF&&c[i]!=c[mnp2]&&w[i]+w[mnp2]<=Y)//与最小球颜色相同 eles if也可以
			G[i].push_back(mnp2),G[mnp2].push_back(i);
	}
	DFS(mnp1);
	for(int i=1;i<=n;i++)
		if(vis[i]==true)
			cnt[c[i]]++;
	int tot=0,ans=1;
	for(int i=1;i<=n;i++)
		tot+=cnt[i];
	for(int i=1;i<=n;i++)
		if(cnt[i])
		{
			ans=1LL*ans*C(tot,cnt[i])%MO;//用法二求出答案
			tot-=cnt[i];
		}
	printf("%d\n",ans);
	return 0;
}

你可能感兴趣的:(图论,exams,组合数学,数论,思维题)