题意:有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了,真特么刺激~