版权声明:编写不易,转载请注明出处,谢谢。 https://blog.csdn.net/WilliamSun0122/article/details/77340605
题意
一个人的名字有名和姓,名和姓上各有n个字符位置,每个位置的字符从m个字符里面选择。问你有多少个人的名字其名和姓上没有相同的字符。
题解
比赛的时候排列组合了半天,差点没写出来。官方题解是用容斥写的,有兴趣的可以看看别人容斥的博客。
当时比赛的时候我正着写和反着写都没写出来,最后正着里面套反写出来了。
解释一下公式(我是把名和姓分开考虑)
答案=(名里面出现一个字符)*(名里面出现一个字符的种数)*(姓里面n个位置全部是m-1个字符中的一个的种数)+(名里面出现两个字符)*(名里面n个位置全部是2个字符中的一个的种数-从2个字符中选择一个*名里面全部是一个字符的种数)*(姓里面n个位置全部是m-2个字符中的一个的种数)+…
上面的公式我总体上是直接正着求名和姓中没有相同字符的名字,但是再求名里面出现n个字符的种数(这里的意思是名里面选出的n个字符必须至少出现一次)的时候我是反着求的,就是先求出n个字符去组成名的总数(
这里的f[i]代表i个字符组成名(这i个字符里面每一个至少出现一次)
f[i]的求解是递推的。
大家把解释结合公式看,要是还不懂可以看看代码。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 2005;
const int mod = 1e9+7;
ll c[maxn][maxn],n,m,f[maxn];
ll pow_mod(ll a,ll b)//快速幂
{
ll res = 1;
while(b)
{
if(b&1) res = (res*a)%mod;
a = (a*a)%mod;
b>>=1;
}
return res;
}
void init()//组合数
{
for(int i=0;i<maxn;i++)
{
for(int j=0;j<=i;j++)
{
if(j==0 || j==i) c[i][j]=1;
else c[i][j] = (c[i-1][j-1]+c[i-1][j])%mod;
}
}
}
int main()
{
int t;
init();
scanf("%d",&t);
while(t--)
{
scanf("%lld%lld",&n,&m);
ll ans=0;
for(int i=1;i<=min(n,m);i++)
{
if(i==1)
{
f[1]=1;
ll res = (c[m][1]*pow_mod(m-1,n))%mod;
ans += res;
ans %= mod;
}
else
{
ll res = (c[m][i]*pow_mod(m-i,n))%mod;
ll tmp = 0;
for(int j=1;j<i;j++)//递推求解f[i]
{
tmp += (c[i][j]*f[j])%mod;
tmp %= mod;
}
f[i] = pow_mod(i,n)-tmp;
res *= f[i];
res %= mod;
ans += res;
ans %= mod;
}
}
printf("%lld\n",ans);
}
return 0;
}