Killer Names

HDU

题意:姓名由姓和名两部分组成,每个部分都含有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;
}

原文地址:https://www.cnblogs.com/PPXppx/p/11428585.html