题意:姓名由姓和名两部分组成,每个部分都含有n个字符,现在一共有m个字符,求姓和名不含相同字符的姓名数量.((n,m<=2000))
分析:我们只要确定了姓的组成,就能求出名的组成方案数.所以我们设(f[i])表示姓的n个字符由i个不同的字符组成的方案数.运用容斥的思想,用总方案数(n个字符由i个字符组成的方案数)减去包含相同字符的方案数,就可以得到(f[i])的表达式了.
(f[i]=i^n-sum_{j=1}^{i-1}f[j]*C_i^j)
得到了(f[i]),也就是姓的组成方案,就很容易得到名的组成方案数.(ans=sum_{i=1}^{min(n,m)}f[i]*C_m^i*(m-i)^n).解释一下这个式子,(f[i]*C_m^i)计算的是姓的方案数,剩下的((m-i))个字符构成名的时候可以随便填,就是((m-i)^n).
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<queue>
#include<map>
#include<set>
#define ll long long
using namespace std;
inline int read(){
int x=0,o=1;char ch=getchar();
while(ch!='-'&&(ch<'0'||ch>'9'))ch=getchar();
if(ch=='-')o=-1,ch=getchar();
while(ch>='0'&&ch<='9')x=x*10+ch-'0',ch=getchar();
return x*o;
}
const int N=2005;
const int mod=1e9+7;
ll jc[N],inv[N],f[N];
inline int ksm(int a,int b){
int cnt=1;
while(b){
if(b&1)cnt=(1ll*cnt*a)%mod;
a=(1ll*a*a)%mod;
b>>=1;
}
return cnt;
}
inline int C(int n,int m){return ((1ll*jc[n]*inv[n-m])%mod*inv[m])%mod;}
int main(){
jc[0]=1;for(int i=1;i<=2000;++i)jc[i]=(1ll*jc[i-1]*i)%mod;
inv[2000]=ksm(jc[2000],mod-2);for(int i=2000-1;i>=0;--i)inv[i]=(1ll*inv[i+1]*(i+1))%mod;//预处理阶乘和逆元
int T=read();
while(T--){
int n=read(),m=read();
for(int i=1;i<=m;++i){
f[i]=ksm(i,n);
for(int j=1;j<=i-1;++j)
f[i]=(f[i]-(1ll*f[j]*C(i,j)%mod)+mod)%mod;//这里一定要加mod,因为可能会减成负数
}//计算f[i]
ll ans=0;
for(int i=1;i<=min(m,n);++i)
ans=(ans+(1ll*f[i]*C(m,i)%mod*ksm(m-i,n))%mod)%mod;
printf("%lld
",ans);
}
return 0;
}