Kings Colors-二项式反演

题目

题目链接

题目大意:给定一个n个节点的树,给它染色并且使得相邻节点异色。问恰好用k种颜色的染色方案数

题目分析

恰好k种不是很好求,因为我们很难保证每种颜色都用到,于是我们先考虑求最多k种颜色。

那么就让每个点和它的父亲节点异色就可以了。

也就是 k ∗ ( k − 1 ) n − 1 k*(k-1)^{n-1} k(k1)n1

那么我们令 f ( i ) f(i) f(i)表示最多用i种颜色的染色方案数

g ( i ) g(i) g(i)表示恰好用i种的染色方案数

那么假设我们要最多用n种,那么不妨从中随意选其中i种,用且仅用这i种颜色染色。

也就是说 f ( n ) = ∑ i = 1 n C n i g ( i ) f(n)=\sum_{i=1}^{n}{C_{n}^{i}g(i)} f(n)=i=1nCnig(i)

然后就要用到二项式反演

g ( n ) = ∑ i = 1 n ( − 1 ) n − i C n i f ( i ) g(n)=\sum_{i=1}^{n}{(-1)^{n-i}C_{n}^{i}f(i)} g(n)=i=1n(1)niCnif(i)

f之前求出来了。问题就解决了。

AC代码

#include<bits/stdc++.h>
using namespace std;
int read(){
	char s;
	int x=0,f=1;
	s=getchar();
	while(s<'0'||s>'9'){
		if(s=='-')f=-1;
		s=getchar();
	}
	while(s>='0'&&s<='9'){
		x*=10;
		x+=s-'0';
		s=getchar();
	}
	return x*f;
}
const long long mod=1000000007;
const int N=2600;
long long qpow(long long a,long long b){
	if(b==0)return 1;
	long long rec=qpow(a,b/2);
	if(b&1)return rec*rec%mod*a%mod;
	return rec*rec%mod;
}
int n,k;
long long f[N];//最多用i种颜色的方案数 
long long calc[N],inv[N];
void init(){
	calc[0]=1;
	for(int i=1;i<=n;i++){
		f[i]=i*qpow((long long)i-1,(long long)n-1)%mod;
		calc[i]=(long long)calc[i-1]*i%mod;
	}
	inv[n]=qpow(calc[n],mod-2);
	for(int i=n-1;i>=0;i--){
		inv[i]=(long long)inv[i+1]*(i+1)%mod;
	}
}
long long C(int i,int j){
	long long sum=calc[i]*inv[j]%mod*inv[i-j]%mod;
	return sum;
}
int main(){
	n=read(),k=read();
	init();
	long long ans=0;
	long long id=1;
	for(int i=k;i>=1;i--){
		ans=(ans+id*f[i]%mod*C(k,i)%mod)%mod;
		ans%=mod;
		id=-id;
	}
	ans=(ans%mod+mod)%mod;
	printf("%lld\n",ans);
	return 0;
}

总结

关于二项式反演,其实就是在恰好、最多(少)之间做转换以达到简化问题的作用。

最难的一步就是找到反演的原式子。

用到了“算两次”的思想,用难算的表示好算的,然后用反演。

其实反演就是容斥,只不过有的时候直接用容斥不太好想。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值