POJ1285Combinations, Once Again题解

原题见:Combinations, Once Again

题意:有x种东西共n个,每种东西num[i]个,问从中取出r个东西有多少种不同的取法。同一种东西无差别,同一组数据的不同排列无差别,即1、2、1和1、1、2是同一种。

输入:

第一行:n(物品总数) m(查询个数)

第二行:n个数表示物品编号(编号在[1,n]),由此可以求出每种物品多少个

第三行:m个数,表示m个查询。

题解:

这是一道基础DP题,但我做了很久。最初,想用一维数组进行DP,想着dp[i]表示n个物品里取i个取法数量,然后,DP方程写不出来,遂卒。。。仔细看这一题,有点像背包(不了解背包的见:https://blog.csdn.net/q1410136042/article/details/80008672),但和背包又有些区别,那么我们是不是可以用背包的思想来写状态转移方程呢?背包最基础的是0-1背包,它的思想是讨论前i个,即由前i-1个的结果转移到前i个的结果。这样一想,DP方程便很好写了。

设dp[i][j]表示前i种物品中取j个的取法,num[i]表示第i种物品的数量,k表示取出来的第i种物品的数量:

                    dp[i][j] = ∑dp[i-1][j-k] (k从0到num[i])

这方程我觉得挺好理解的,就不多做解释了。

代码:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<cmath>
#include<algorithm>
#define ll long long

using namespace std;
int main()
{
    #ifdef AFei
    freopen("in.txt", "r", stdin);
    #endif // AFei

    int m, n, _case = 0;
    int num[55];
    ll dp[55][55];
    while(scanf("%d%d", &n, &m), n)
    {
        _case ++;
        memset(num, 0, sizeof num);
        memset(dp, 0, sizeof dp);
        for(int x, i = 0; i < n; ++ i)
        {
            scanf("%d", &x);
            num[x-1] ++;
        }

        for(int i = 0; i <= num[0]; ++ i)
            dp[0][i] = 1;

        for(int i = 1; i < n; ++ i)
        {
            for(int j = 0; j <= n; ++ j)
            {
                for(int k = 0; k <= num[i] && k <= j; ++ k)
                    dp[i][j] += dp[i-1][j-k];
            }
        }
        printf("Case %d:\n", _case);
        for(int r, i = 0; i < m; ++ i)
        {
            scanf("%d", &r);
            printf("%lld\n", dp[n-1][r]);
        }
    }
    return 0;
}

话说POJ崩的简直让人绝望,一开始使用VJ交,好几次都是Submit Failed。。。后来直接上POJ,waiting了两页,第三页才看到我的提交,果断退出去了,十分钟后回来看,AC了,真特么刺激~

猜你喜欢

转载自blog.csdn.net/q1410136042/article/details/80057922