Hello,大家好,这里是大千小熊的博客,大千小熊是一只又会MMD又会C++的正派小熊,欢迎同时关注B站账号~写题目累了,就看看MMD放松一下。
题目描述:
题目: 有n种物品, 第i种物品有a个. 不同种类的物品可以互相区分, 但相同种类的无法区分.
从这些物品中取出m个, 有多少种取法? 求出数模M的余数.
例如: 有n=3种物品, 每种a={1,2,3}个, 取出m=3个, 取法result=6(0+0+3, 0+1+2, 0+2+1, 1+0+2, 1+1+1, 1+2+0).
题解思路:
首先您要注意的是,数组的下标是从0开始的,i+1和i才是对应的关系。比如当i=0,那么a[0]是第一种物品,dp[1][*]同样也是在分析a[0]虽然dp数组的第一个参数是1。
这是一道典型的动态规划问题,我们可以从第i种物品和第i+1种物品之间的关系招手分析,假设一个dp[i][j]的数组的含义是:从0到i,组合出j个的方案总数,并且假设这个dp数组里面存放的所有的答案是正确的。
那么现在让我们分析第i+1种物品,第i+1种物品可以放K个,K可以取0,第i+1种物品一个不放,K可以取1,第i+1种物品先放1个,那么第i种物品放K-1个。第i+1种物品最多能放min(j,a[i])。
所以最后我们得到了一个递推公式:
优化思路:
那么这个程序有i,有j,有k。是一个三维的循环。
有没有什么办法可以将这个三重循环降至二维循环呢?仔细观察上面的递推关系,发现还是有的状态被重复读写了。让我们来看看上面这个递推式。
情况1:j<=a[i]
那么这个方程展开是这个样子。
然后我们再次仔细观察这个方程,特别是有颜色的地方。
实际上有颜色的这个部分就是dp[i+1][j-1]。
所以最终dp[i+1][j]=dp[i][j]+dp[i+1][j-1]。
情况2:j>a[i]
有了上面的基础,那么这种问题其实也很简单。
相比于情况1,就是多出来了,dp[i][j-a[i]-1]到dp[i][0]这部分的组合数。
那我们减去这些多出来的不就是答案了吗?那么这些数的总和是多少呢,在这里我使用了一个sum数组专门缓存这些数字的总和。sum[i][j-a[i]-1]就是从dp[i][0]到dp[i][j-a[i]-1]的所有组合数的总和。您可以在下面的代码中体现出来。
代码详解:
#include <cmath>#include<iomanip>#include<iostream>using namespace std;
unsigned int n = 0, m = 0, a[1003] = {0}, M = 0, dp[1003][1003] = {0},
sum[1003][1003] = {0};
unsigned int minx(int a, int b) { return a > b ? b : a; }
int main() {
scanf("%d", &n);
scanf("%d", &m);
for (int i = 0; i < n; i++) {
scanf("%d", &a[i]);
}
scanf("%d", &M);
//任何一个不放的都是1,因为别的位置放满了。所以当然是1种了。
for (int i = 0; i <= n; i++) {
dp[i][0] = 1;
sum[i][0] = 1;
}
for (int i = 0; i < n; i++) {
for (int j = 1; j <= m; j++) {
if (i == 0)
sum[i][j] = 1;
if (j > a[i]) {
dp[i + 1][j] = (sum[i][j] - sum[i][j - a[i] - 1])%M;
sum[i + 1][j] = (sum[i + 1][j - 1] + dp[i + 1][j] + M)%M;
} else {
dp[i + 1][j] = (dp[i][j] + dp[i + 1][j - 1])%M;
sum[i + 1][j] = (sum[i + 1][j - 1] + dp[i + 1][j] + M)%M;
}
}
}
printf("%d",dp[n][m]);
return 0;
}
补充书本上的算法:
在【日】秋叶拓哉《挑战程序设计竞赛(第二版)》Page68中给出的递推式中:
情况1:j-1-a[i]>=0
dp[i+1][j]=dp[i+1][j-1]+dp[i][j]-dp[i][j-1-a[i]]
情况2:是一模一样的。
dp[i+1][j]=dp[i+1][j-1]+dp[i][j]
问题的焦点就是在于“情况1”的dp[i][j]-dp[i][j-1-a[i]]是什么东西呢?
让我们来看看(输入数据):
n=4
m=4
a[]={ 1 ,2 ,2 ,4 }
M=1000
让我们观察一下dp[3][2],它是dp[2][2]+dp[2][1]+dp[2][0]。
dp[2][2]+dp[2][1]+dp[2][0]
同理,dp[3][3]=dp[2][3]+dp[2][2]+dp[2][1]。
dp[3][3]=dp[2][3]+dp[2][2]+dp[2][1]
为什么总是有三项相加?这是因为第i+1(第3个物品)它只有两个,它有三种情况:取1个,2个,0个。
那么现在我们看看dp[3][3]=dp[3][2]+dp[2][3]是什么情况。
dp[3][2]+dp[2][3]
但是实际上比我们要求的样子多出来了一个dp[2][0]这个dp[2][0]就是dp[2][3-a[2]-1]。
所以课本上面的递推式要减去的就是那一个多出来的dp[i][j-a[i]-1]。