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
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mv4ztwR8-1616242430609)(https://www.bilibili.com//i0.hdslb.com/bfs/article/805f1054d4f3ede0e4c4317a2983e487cfba132a.png@1236w_366h.webp)]](https://i-blog.csdnimg.cn/blog_migrate/281699a2bfa8405a4e55295a9b718a5b.png)
让我们观察一下dp[3][2],它是dp[2][2]+dp[2][1]+dp[2][0]。
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-38ESKP3Z-1616242430610)(https://www.bilibili.com//i0.hdslb.com/bfs/article/a3aaa92c9de214d6309c7d657678240b8ce36074.png@1236w_362h.webp)]](https://i-blog.csdnimg.cn/blog_migrate/bb644ef984d3b02290ef46187bbc498c.png)
dp[2][2]+dp[2][1]+dp[2][0]
同理,dp[3][3]=dp[2][3]+dp[2][2]+dp[2][1]。
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FzR6vGGi-1616242430611)(https://www.bilibili.com//i0.hdslb.com/bfs/article/edcb8a530380a97f36e68e41a5b435cf4006e6d7.png@1236w_358h.webp)]](https://i-blog.csdnimg.cn/blog_migrate/9ee7570d0e7408a9b042d321cf6a188f.png)
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]是什么情况。
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gESP3m8J-1616242430611)(https://www.bilibili.com//i0.hdslb.com/bfs/article/89cbb2e7d5f685540203d9ad97a74cb61dfd4f21.png@1236w_370h.webp)]](https://i-blog.csdnimg.cn/blog_migrate/f28ec5169bf87eaa4b5e33c6be23841f.png)
dp[3][2]+dp[2][3]
但是实际上比我们要求的样子多出来了一个dp[2][0]这个dp[2][0]就是dp[2][3-a[2]-1]。
所以课本上面的递推式要减去的就是那一个多出来的dp[i][j-a[i]-1]。


被折叠的 条评论
为什么被折叠?



