计数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]\)可以用如下图表示:

figure_1
  • 情况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. \]

用图像进行表示如下:

figure_2

因此,可以证明上述表达式成立。

核心代码如下:

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];
		}
	}
}

Update: 2020/8/16

经典例题:POJ3046 - Ant Counting

posted @ 2020-07-04 11:41  Last_Whisper  阅读(450)  评论(1编辑  收藏  举报