有关计数问题的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]=j的i划分的总数
把 n n n件物品分成至多 m m m份 = = = 把 n n n件物品分成至多 m − 1 m-1 m−1份 + + +把 n n n件物品分成刚好 m m m份
对“把 n n n件物品分成刚好 m m m份”进行分析:把每一份都减一后就像相当于把 n − m n-m n−m件物品分成至多 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][j−i]+dp[i−1][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 i−1种物品中取出 j − k j-k j−k个,再从第 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=0∑min(j,a[i])dp[i−1][j−k]
这时候时间复杂度是 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[i−1][j]+dp[i−1][j−1]+⋯+dp[i−1][j−a[i]]=dp[i−1][j]+dp[i][j−1]−dp[i][j−1−a[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[i−1][j]+dp[i−1][j−1]+⋯+dp[i−1][0]=dp[i−1][j]+dp[i][j−1]
这时候时间复杂度就降到 O ( n m ) O(nm) O(nm)了


950

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



