计数DP --- 多重组合数问题#
多重集组合数,有n种物品,第 i 种有ai个。不同种类的物品可以互相区分但是相同的种类的无法区别。从这些物品中取m个有多少种取法。
限制条件:
1≤n≤1000
1≤m≤1000
1≤ai≤1000
2≤M≤10000
这是一个比较经典的计数dp问题,我们设dp[i+1][j] := 从前i种物品中取出j个的组合总数。
暴力法思维量较小,应用递推。我们可以把从前i种物品取j个,划分为:从前i−1种物品中取j−k个,从剩下的第i种物品里取剩下的k个。则递推式可以表示为:
dp[i+1][j]=min{j,a[i]}∑k=0dp[i][j−k]
但是利用这个递推式进行计算的时间复杂度为O(nm2),根据限制条件,该算法时间复杂度过高,需要进行优化。
我们对递推式进行优化,优化后的结果如下,下面将阐明如何得到这个递推式的:
dp[i+1][j]=dp[i+1][j−1]+dp[i][j]−dp[i][j−1−ai]
分两种情况:
这时候dp[i+1][j−1]的k的取值范围是[0,j−1]:
dp[i+1][j−1]=dp[i][j−1]+dp[i][j−2]+⋯+dp[i][1]+dp[i][0]
又因为a[i]>j−1⇒a[i]≥j,这时k的取值范围是[0,j]:
dp[i+1][j]=dp[i][j]+dp[i][j−1]+⋯+dp[i][1]+dp[i][0]
可以发现dp[i+1][j]与dp[i+1][j−1]之间只差一个dp[i][j]可以用如下图表示:
这时,将式子进行展开得到:
{dp[i+1][j]=dp[i][j]+dp[i][j−1]+⋯+dp[i][j−a[i]]dp[i+1][j−1]=dp[i][j−1]+⋯+dp[i][j−1−a[i]]
用图像进行表示如下:
因此,可以证明上述表达式成立。
核心代码如下:
Copy
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
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· winform 绘制太阳,地球,月球 运作规律
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人