【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[minpos[c[i]]]+w[i]&lt;=Xw[minpos[c[i]]]+w[i]&lt;=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標記的點來說,要麼是根本就沒有連邊,要麼就是與自己顏色的最小球連了邊,然而後者是沒有用的,因爲是求顏色序列的方案數。然後相當於就是那些球的位置就已經固定死了,假如自由球的序列已經確定了,那麼這些球的位置也相對固定了。

然後對於自由球來說,就是一個重排的問題了。可以用重排的公式求出,也可以直接用Ctotcntcolori\sum C_{tot}^{cnt_{color_i}}求出。那麼問題得到解決。

代碼:

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
#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;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章