有关计数问题的DP

有关计数问题的DP

划分数

题目:放苹果_牛客网

描述

n n n个无区别的物品,将它们划分成不超过 m m m组,求出划分方法数模 M M M的余数。 ( 1 < = m < = n < = 1000 , 2 < = M < = 10000 ) (1<=m<=n<=1000, 2<=M<=10000) (1<=m<=n<=1000,2<=M<=10000)

输入

n = 4
m = 3
M = 10000

输出

4

题解

定义 d p [ i ] [ j ] = j 的 i 划分的总数 dp[i][j] = j的i划分的总数 dp[i][j]=ji划分的总数

n n n件物品分成至多 m m m = = = n n n件物品分成至多 m − 1 m-1 m1 + + + n n n件物品分成刚好 m m m

对“把 n n n件物品分成刚好 m m m份”进行分析:把每一份都减一后就像相当于把 n − m n-m nm件物品分成至多 m m m

所以有: d p [ i ] [ j ] = d p [ i ] [ j − i ] + d p [ i − 1 ] [ j ] dp[i][j] = dp[i][j - i] + dp[i-1][j] dp[i][j]=dp[i][ji]+dp[i1][j]

代码
#include <bits/stdd++/h>
#define maxn 30
#define _for(i, a) for(LL i = 0; i < (a); i++)
#define _rep(i, a, b) for(LL i = (a); i <= (b); i++)
using namespace std;
typedef long long LL;
const LL inf = 0x3f3f3f3f;

int dp[maxn][maxn];

int main() {
	ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
	//freopen("in.txt", "r", stdin);

	int n, m;
	while (cin >> n >> m) {
		memset(dp, 0, sizeof(dp));
		dp[0][0] = 1;
		_rep(i, 1, m) {
			_for(j, n + 1) {
				if (j >= i) {
					dp[i][j] = dp[i][j - i] + dp[i - 1][j];
				}
				else {
					dp[i][j] = dp[i - 1][j];
				}
			}
		}
		cout << dp[m][n] << "\n";
	}
	return 0;
}

多重集组合数

描述

n n n种物品,第 i i i种物品有 a i a_i ai个,不同种类的物品可以互相区分,但相同种类的物品无法区分。从这些物品中取出 m m m个的话,有多少种取法?求出方案数模 M M M的余数。 ( 1 < = n < = 1000 , 1 < = m < = 1000 , 1 < = a i < = 1000 , 2 < = M < = 10000 ) (1<=n<=1000, 1<=m<=1000, 1<=a_i<=1000,2<=M<=10000) (1<=n<=1000,1<=m<=1000,1<=ai<=1000,2<=M<=10000)

输入

n = 3
m = 3
a = {1, 2, 3}
M = 10000

输出

6

题解

定义 d p [ i ] [ j ] = 前 i 种物品中取出 j 个的组合总数 dp[i][j] = 前i种物品中取出j个的组合总数 dp[i][j]=i种物品中取出j个的组合总数

所以为了从前 i i i中物品中取出 j j j个,可以从前 i − 1 i-1 i1种物品中取出 j − k j-k jk个,再从第 i i i种物品拿出 k k k

于是有:
d p [ i ] [ j ] = ∑ k = 0 m i n ( j , a [ i ] ) d p [ i − 1 ] [ j − k ] dp[i][j]=\sum\limits_{k=0}^{min(j,a[i])}{dp[i-1][j-k]} dp[i][j]=k=0min(j,a[i])dp[i1][jk]

这时候时间复杂度是 O ( n m 2 ) O(nm^{2}) O(nm2),显然还是可以优化的

首先把求和式展开,注意到在k的上限里有一个取最小值,所以这里要分两种情况

  • j > a [ i ] j>a[i] j>a[i]

d p [ i ] [ j ] = d p [ i − 1 ] [ j ] + d p [ i − 1 ] [ j − 1 ] + ⋯ + d p [ i − 1 ] [ j − a [ i ] ] = d p [ i − 1 ] [ j ] + d p [ i ] [ j − 1 ] − d p [ i ] [ j − 1 − a [ i ] ] \begin{aligned} dp[i][j]&=dp[i-1][j]+dp[i-1][j-1]+\cdots+dp[i-1][j-a[i]] \\ &=dp[i-1][j]+dp[i][j-1]-dp[i][j-1-a[i]] \end{aligned} dp[i][j]=dp[i1][j]+dp[i1][j1]++dp[i1][ja[i]]=dp[i1][j]+dp[i][j1]dp[i][j1a[i]]

  • j < = a [ i ] j<=a[i] j<=a[i]

d p [ i ] [ j ] = d p [ i − 1 ] [ j ] + d p [ i − 1 ] [ j − 1 ] + ⋯ + d p [ i − 1 ] [ 0 ] = d p [ i − 1 ] [ j ] + d p [ i ] [ j − 1 ] \begin{aligned} dp[i][j]&=dp[i-1][j]+dp[i-1][j-1]+\cdots+dp[i-1][0] \\ &=dp[i-1][j]+dp[i][j-1] \end{aligned} dp[i][j]=dp[i1][j]+dp[i1][j1]++dp[i1][0]=dp[i1][j]+dp[i][j1]

这时候时间复杂度就降到 O ( n m ) O(nm) O(nm)

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值