HDU

来源:互联网 发布:网络世界边锋游戏 编辑:程序博客网 时间:2024/05/21 22:27

题目描述:

点击打开链接

题意,每个名字由姓和名组成,姓和名都有n个字母,要求姓和名中不能出现相同的字母,现给你m种不同的字母,问你可以组成多少种不同的名字。

起初看到很多关于容斥或者组合数的计数方式,但是理解起来都很复杂,偶然看到一个DP的写法,觉得非常好,于是在这里记录下来。这题一个关键的地方就在于如何计算n个位置使用x种不同的字母组成的字符串有多少种,设dp[i][j]为i个位置使用j种字母的方案数,显然dp[1][1]=m那么接下来转移,假如前i-1个位置已经使用了j种字母,那么这个位置可以在j种字母中任选,假如前i-1个位置只使用j-1种字母,那么这个位置就需要从剩下的m-j+1种字母中任选一个,所以状态转移方程为dp[i][j]=dp[i-1][j]*j+dp[i-1][j-1]*(m-j+1),解决这个问题之后剩下就是姓和名的组合问题,假设姓使用了 i种字母,方案数dp[n][i],那么名就要将剩下的(m-i)种字母放在n个位置,这种方案数可以直接确定为(m-i)^n因为这样计算直接将名使用了1到m-i种字母的方案全部算进去了的,而姓的计算方式中dp时其实是把C(m,i)也直接算进去了的,那么这种情况下的方案数就直接是dp[n][i]*(m-i)^n这样计算就省去组合数容斥的麻烦处理。

AC代码:

#include<iostream>#include<cstdio>#include<cstring>#include<string>#include<cmath>#include<stack>#include<vector>#include<queue>#include<algorithm>using namespace std;const long long MOD=1e9+7;const int MAXM=2010;long long dp[MAXM][MAXM];long long n,m;long long qpow(long long x,long long y){    long long res=1;    while(y)    {        if (y&1) res=(res*x)%MOD;        x=(x*x)%MOD;        y=y>>1;    }    return res;}int main(){    int T;    scanf("%d",&T);    while(T--)    {        cin>>n>>m;        dp[1][1]=m;        for (int i=2;i<=n;i++)        {            for (int j=1;j<=i;j++)            {                if (j>m) break;                dp[i][j]=(dp[i-1][j]*j%MOD+dp[i-1][j-1]*(m-j+1)%MOD)%MOD;            }        }        long long ans=0;        for (long long i=1;i<=m;i++)            ans=(ans+dp[n][i]*qpow(m-i,n)%MOD)%MOD;        cout<<ans<<endl;    }    return 0;}


原创粉丝点击