http://acm.split.hdu.edu.cn/showproblem.php?pid=6143
题意:有m个字符,由你来取名字,姓和名。一个字符只能出现在姓或者名,或者不出现。姓和名的长度为n。求可以取多少个不重复的名字。
题解:一开始的思路:姓里面放i个字符,就是i^n;名里面还可以选m-i个字符,就是(m-i)^n;再乘上组合数,答案就是sum(C(m,i)*i^n*(m-i)^n),i∈[1,m]。
上面那个就是公式,写几个后会发现,姓里面有重复计算的部分,要减去这一部分。
dp[i]:m里面取i个放在姓中,这i个都必须出现(i^n包含了出现小于i个字符的情况)。
比如dp[3]=3^n-C(3,2)*(2^n-C(2,1)*1^n)-C(3,1)*1^n。这里好好理解一下,是去重)。
上式转化就是:dp[3]=3^n-C(3,2)*dp[2]-C(3,1)*dp[1]。
所以有递推方程:
dp[i]=i^n-C(i,i-1)*dp[i-1]-C(i,i-2)*dp[i-2]-...-C(i,1)*dp[1]
答案就是sum(C(m,i)*dp[i]*(m-i)^n),i∈[1,m](组合数*姓*名)。
代码:
#include<bits/stdc++.h>
#define debug cout<<"aaa"<<endl
#define d(a) cout<<a<<endl
#define mem(a,b) memset(a,b,sizeof(a))
#define LL long long
#define lson l,mid,root<<1
#define rson mid+1,r,root<<1|1
#define MIN_INT (-2147483647-1)
#define MAX_INT 2147483647
#define MAX_LL 9223372036854775807i64
#define MIN_LL (-9223372036854775807i64-1)
using namespace std;
const int N = 2000 + 5;
const LL mod = 1000000000 + 7;
const double eps = 1e-8;
LL quickMod(LL a,LL b){
LL ans=1;
a%=mod;
while(b){
if(b&1)
ans=(ans*a)%mod;
a=(a*a)%mod;
b>>=1;
}
return ans;
}
LL quick(LL a,LL b){
LL ans=0;
a%=mod;
while(b){
if(b&1)
ans=(ans+a)%mod;
a=(a+a)%mod;
b>>=1;
}
return ans;
}
LL C[N][N],dp[N];
void Init(){
mem(C,0);
for(int i=1;i<N;i++){
C[i][0]=1;C[i][i]=1;
for(int j=1;j<i;j++){
C[i][j]=((C[i-1][j-1]+C[i-1][j])%mod+mod)%mod;
}
}
}
int main(){
int t;
Init();
LL n,m,ans,a;
scanf("%d",&t);
while(t--){
ans=0,mem(dp,0);
scanf("%lld%lld",&n,&m);
//打表递推数组,这是姓的部分,要去重
for(LL i=1;i<=m;i++){
dp[i]=quickMod(i,n);
for(int j=1;j<i;j++){
dp[i]=((dp[i]-C[i][j]*dp[j])%mod+mod)%mod;
}
}
//求和:C(m,i)*dp[i]*(m-i)^n
for(LL i=1;i<m;i++){
a=quick(dp[i],quickMod(m-i,n));
ans+=quick(C[m][i],a);
ans%=mod;
}
printf("%lld\n",ans);
}
return 0;
}