挣扎了好几天终于把这一题过了。。比赛的时候RP低到爆忘记带费马小定理和快速幂的模板,好不容易YY出了一个组合数的求法,然后发现公式推错了,最后呵呵呵呵。><
一开始的想法是C(m,k)k(k-1)^(n-1),但是这种case包括的是颜色数<=k的情形,所以还要减去颜色数<=(k-1)的情形。
C(m,k)[k(k-1)^(n-1)-(k-1)(k-2)^(n-1)]
下面考虑这种case,如果选取的k个颜色是1,2,3,4,那么会有一种染色方案是1,2,1,2...,如果选取的k-1个颜色是1,2,3,那么仍然会包含一种染色方案1,2,1,2...所以上面的公式减多了,还要再加上颜色数<=(k-2)的case,然后再减去颜色数<=(k-3)的case。
考虑有k种颜色时的染色方案:sum (-1)^i*C(k,i)*(k-i)*(k-i-1)^(n-1) for i=0,1,...,k
总共有m种颜色可以选择,所以再乘上C(m,k)即可。
因为涉及到组合数取模,所以需要求逆元,因为只有乘法才可以边乘边取模。由于1e9+7是质数,求逆元可以用用费马小定理,a^(p-1)=1%p 那么a^(p-1)/a =a^(p-2)= 1/a%p ,其中gcd(a,p)=1,p is a prime。最后用快速幂求解即可。
1,2,3....的逆元可以预处理。组合数可以递推求解。
一直WA on test 2 ,最后发现是相乘的过程中少%mod,估计是中途爆long long了。难怪小数据对拍都查不出来==
#include<iostream>
#include<stdio.h>
#include<cstdio>
#include<stdlib.h>
#include<vector>
#include<string>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<stack>
#include<queue>
#include<ctype.h>
#include<map>
#include<time.h>
#include<bitset>
using namespace std;
//Xi'an Reigonal F
int T;
long long n;
long long m;
long long k;
const long long mod=1000000007;
const int maxn=1000010;
long long comb[maxn];
long long inv[maxn];
long long ans;
long long pow_mod(long long a,long long b)
{
long long s=1;
long long t=1;
while(b)
{
if(b&t)
{
s=(s*a)%mod;
}
a=(a*a)%mod;
b=b>>1;
}
return s;
}
void cal_inv()
{
for(int i=1;i<maxn;i++)
{
inv[i]=pow_mod(i,mod-2)%mod;
}
}
void cal_comb(long long m0,long long k0)//C(m,0),C(m,1),C(m,2)....C(m,k)
{
comb[0]=1;
for(long long i=1;i<=k0;i++)
{
comb[i]=((comb[i-1]*(m0-i+1))%mod)*inv[i]%mod;
//cout<<i<<" " <<comb[i]<<endl;
}
}
void solve()
{
cal_comb(m,k);
ans=comb[k]%mod;
memset(comb,0,sizeof(comb));
long long ret=1;
cal_comb(k,k);
long long tmp=0;
// for(int i=k;i>0;i--) //equivalent to the following one, if we replace i with k-j
// {
// tmp+=ret*i*pow_mod(i-1,n-1)%mod*comb[i]%mod;
// tmp=(tmp+mod)%mod;
// ret=-ret;
// }
for(long long i=0;i<=(k-1);i++)
{
tmp+=ret*(k-i)*comb[i]%mod*pow_mod(k-i-1,n-1)%mod;//之前写成了tmp+=ret*comb[i]%mod*(k-i)*pow_mod(k-i-1,n-1);然后一直WA on test 2,估计是中间的相乘爆long long了。
//tmp+=ret*comb[i]%mod*(k-i)%mod*pow_mod(k-i-1,n-1)%mod; //it‘s also ok.
tmp=(tmp+mod)%mod;
ret=-ret;
}
ans=(ans*tmp)%mod;
}
int main()
{
//freopen("input.txt","r",stdin);
freopen("data.txt","r",stdin);
freopen("out1.txt","w",stdout);
scanf("%d",&T);
memset(inv,0,sizeof(inv));
cal_inv();
for(int ca=1;ca<=T;ca++)
{
memset(comb,0,sizeof(comb));
ans=1;
scanf("%I64d %I64d %I64d",&n,&m,&k);
if(n==1&&m==1)
{
ans=1;
}
else
{
solve();
}
printf("Case #%d: %I64d\n",ca,ans);
}
return 0;
}
顺便再附上暴力打表验证的的代码,其实数据大一点基本就跑不出来了==
#include<iostream>
#include<algorithm>
#include<map>
#include<set>
#include<stdlib.h>
#include<stdio.h>
#include<queue>
#include<cstring>
#include<time.h>
using namespace std;
//Xi'an Reigonal F BF
const int maxn=1000010;
int T;
int n;
int m;
int k;
long long ans;
int s[maxn];
int used[maxn];
void dfs(int dep)
{
if(dep==n)
{
memset(used,0,sizeof(used));
int cnt=0;
for(int i=1;i<=n;i++)
{
int t=s[i];
if(used[t]==0)
{
cnt++;
used[t]=1;
}
}
if(cnt==k)
{
ans++;
for(int i=1;i<=n;i++)
{
printf("%d ",s[i]);
}
puts("");
}
return;
}
for(int i=1;i<=m;i++)
{
if(s[dep]!=i)
{
s[dep+1]=i;
dfs(dep+1);
s[dep+1]=0;
}
}
}
int main()
{
//freopen("input.txt","r",stdin);
freopen("data.txt","r",stdin);
freopen("out2.txt","w",stdout);
scanf("%d",&T);
for(int ca=1;ca<=T;ca++)
{
scanf("%d %d %d",&n,&m,&k);
ans=0;
memset(s,0,sizeof(s));
dfs(0);
printf("Case #%d: %I64d\n",ca,ans);
}
return 0;
}