题目
题目大意:给定一个n个节点的树,给它染色并且使得相邻节点异色。问恰好用k种颜色的染色方案数
题目分析
恰好k种不是很好求,因为我们很难保证每种颜色都用到,于是我们先考虑求最多k种颜色。
那么就让每个点和它的父亲节点异色就可以了。
也就是 k ∗ ( k − 1 ) n − 1 k*(k-1)^{n-1} k∗(k−1)n−1
那么我们令 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)n−iCnif(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;
}
总结
关于二项式反演,其实就是在恰好、最多(少)之间做转换以达到简化问题的作用。
最难的一步就是找到反演的原式子。
用到了“算两次”的思想,用难算的表示好算的,然后用反演。
其实反演就是容斥,只不过有的时候直接用容斥不太好想。