agc012_d Colorful Balls (连通块 贪心 结论 排列组合)

这题乍一看没有什么头绪,有点凌乱。

不妨建一个更加明晰的问题模型

有n个球,分成k堆,第 i 堆球的颜色都是c[i],

每个球 j 都有一个重量w[j],且标有一个1~n的数字(代表它在序列中的位置)。

每次你能做的操作就是交换某两个球的数字,并且

同一堆的两个球能够交换数字,当且仅当重量和<=X;

不同堆的两个球能够交换数字,当且仅当重量和<=Y。

问最后有多少种本质不同的填数方案(两种方案本质不同当且仅当存在某个数字x所在的堆不同)。

如何计算答案?

我们考虑哪些球之间可以交换数字(可以交换的连一条边)。

我们记第i堆中重量最小的球为Min_i

(1)同一堆内(第P堆),如果W_i+W_j<=X,那么存在边(i,j)

但我们发现,事实上我们只需要保留形如(i,Min_P)的边就足够了,因为i和j可以通过Min_P互相连通。

(2)不同的两堆(第A堆和第B堆),

枚举第A堆的球i和第B堆的球j,如果W_i+W_j<=Y,那么存在边(i,j)

但我们发现,事实上我们只需要保留形如(i,Min_B)以及(j,Min_A)的边就足够了。

因为如果存在(i,j),那么也会存在(i,Min_B)(j,Min_A)以及(Min_A,Min_B),去掉(i,j)仍然可以保证i和j连通。

(3)更一般地,考虑若干堆之间的联系。

假设所有堆已经按照Min_i递增的顺序排列。

对于形如(x,Min_A)的边我们也可以去掉,只需要保留(x,Min_1)

---------------

对于一个堆i,如果不存在边(Min_i,Min_1),那么这个堆显然无法和外界连通,并不会影响答案,可以忽略它了。

否则,这个堆里有两种球x:

(1)不存在边(x,Min_1),也不存在边(x,Min_i),那这个球就等于被孤立了,可以忽略它了;

(2)存在边(x,Min_1)(x,Min_i),这个球就可以和Min_1连通。

现在我们发现,除了被忽略的球以外,其它所有球都和Min_1连通,彼此都可以交换数字。

记一共有sum个球彼此连通,每个堆的球数(不考虑忽略的)为cnt_i,答案=\frac{sum!}{cnt_1!cnt_2!...cnt_k!}

#include <cstdio>
#include <algorithm>
#define fs first
#define sc second
#define mp make_pair
#define ll long long
#define org b[1].sc
#define rep(i,j,k) for (i=j;i<=k;i++)
#define down(i,j,k) for (i=j;i>=k;i--)
using namespace std;
const int N=2e5+5,mod=1e9+7;
typedef pair<int,int> Pair;
Pair a[N],b[N];
int n,bn,col,X,Y,i,sum,ans;
int fac[N],inv[N],cnt[N],flg[N],Min[N],con[N];
int fgm(int a,int n)
{
	int ret=1,bsc=a;
	for (;n>0;n>>=1,bsc=(ll)bsc*bsc%mod)
		if (n&1) ret=(ll)ret*bsc%mod;
	return ret;
}
int main()
{	
	scanf("%d%d%d",&n,&X,&Y);
	rep(i,1,n) scanf("%d%d",&a[i].sc,&a[i].fs);
	sort(a+1,a+1+n);
	rep(i,1,n)
		if (!flg[a[i].sc]) b[++bn]=a[i],Min[a[i].sc]=a[i].fs,flg[a[i].sc]=1;
	if (bn==1 || b[1].fs+b[2].fs>Y) { printf("1\n"); return 0; }
	
	rep(i,2,bn) {
		if (b[i].fs+b[1].fs>Y) break;
		con[b[i].sc]=1;
	}
	
	rep(i,1,n)
	{
		col=a[i].sc;
		if (col==org) {
			if (a[i].fs+Min[col]<=X || a[i].fs+b[2].fs<=Y) cnt[col]++;
		}
		else
		if (con[col]) {
				if (a[i].fs+Min[col]<=X || a[i].fs+Min[org]<=Y) cnt[col]++;
		}
	}
	
	fac[1]=1; rep(i,2,n) fac[i]=(ll)fac[i-1]*i%mod;
	inv[n]=fgm(fac[n],mod-2);
	down(i,n-1,1) inv[i]=(ll)inv[i+1]*(i+1)%mod;
	
	ans=1;
	rep(i,1,n)
		if (cnt[i]) sum+=cnt[i],ans=(ll)ans*inv[cnt[i]]%mod;
	ans=(ll)ans*fac[sum]%mod;
	printf("%d\n",ans);
	return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值