【题解】多重集组合数问题·动态规划C/C++

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)]

让我们观察一下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)]

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)]

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)]

dp[3][2]+dp[2][3]

但是实际上比我们要求的样子多出来了一个dp[2][0]这个dp[2][0]就是dp[2][3-a[2]-1]。

所以课本上面的递推式要减去的就是那一个多出来的dp[i][j-a[i]-1]。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值