计数DP --- 多重组合数问题
计数DP --- 多重组合数问题
多重集组合数,有\(n\)种物品,第 i 种有\(a_i\)个。不同种类的物品可以互相区分但是相同的种类的无法区别。从这些物品中取\(m\)个有多少种取法。
限制条件:
\(1 \leq n \leq 1000\)
\(1 \leq m \leq 1000\)
\(1 \leq a_i \leq 1000\)
\(2 \leq M \leq 10000\)
这是一个比较经典的计数dp问题,我们设dp[i+1][j] := 从前i种物品中取出j个的组合总数。
暴力法思维量较小,应用递推。我们可以把从前\(i\)种物品取\(j\)个,划分为:从前\(i - 1\)种物品中取\(j-k\)个,从剩下的第\(i\)种物品里取剩下的\(k\)个。则递推式可以表示为:
\[dp[i+1][j] = \sum_{k=0}^{\min\{j, a[i]\}}dp[i][j-k]
\]
但是利用这个递推式进行计算的时间复杂度为\(\mathcal{O(nm^2)}\),根据限制条件,该算法时间复杂度过高,需要进行优化。
我们对递推式进行优化,优化后的结果如下,下面将阐明如何得到这个递推式的:
\[dp[i+1][j] = dp[i + 1][j - 1] + dp[i][j] - dp[i][j - 1 - a_i]
\]
分两种情况:
- 情况1: \(a[i] > j - 1\)
这时候\(dp[i + 1][j - 1]\)的k的取值范围是\([0, j - 1]\):
\[dp[i + 1][j - 1] = dp[i][j-1] + dp[i][j-2] + \cdots + dp[i][1] + dp[i][0]
\]
又因为\(a[i] > j - 1 \Rightarrow a[i] \ge j\),这时k的取值范围是\([0, j]\):
\[dp[i+1][j] = dp[i][j] + dp[i][j - 1] + \cdots + dp[i][1] + dp[i][0]
\]
可以发现\(dp[i+1][j]与dp[i+1][j-1]\)之间只差一个\(dp[i][j]\)可以用如下图表示:
- 情况2:\(a[i] \leq j - 1\)
这时,将式子进行展开得到:
\[\left\{\begin{array}
xdp[i+1][j] = dp[i][j] + dp[i][j-1] + \cdots + dp[i][j-a[i]] \\
dp[i+1][j-1] = dp[i][j-1] + \cdots + dp[i][j-1-a[i]] \\
\end{array}\right.
\]
用图像进行表示如下:
因此,可以证明上述表达式成立。
核心代码如下:
int n, m;
int a[MAX_N]
int dp[MAX_n][MAX_M]
void solve(){
for (int i = 0; i <= n; ++ i) dp[i][0] = 1;
for (int i = 0; i < n; ++ i){
for (int j = 1; j <= m; ++ j){
if (j - 1 - a[i] >= 0){
dp[i + 1][j] = dp[i + 1][j - 1] + dp[i][j] - dp[i][j - 1 - a[i]];
}else dp[i + 1][j] = dp[i + 1][j - 1] + dp[i][j];
}
}
}