dp 部分和问题及其扩展
Posted 桂月二四
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了dp 部分和问题及其扩展相关的知识,希望对你有一定的参考价值。
**
一.部分和问题
**
给定整数 a1、a2、…、an,判断是否可以从中选出若干数,使它们的和恰好为 k。
首先,我们定义dp数组 dp[i][j] 代表使用前i个数字能否组成j这个数字
其次,我们发现dp[i][j] 是可以由dp[i-1][j-a[i]] 推出的,因此我们由状态转移方程 dp[i][j] |= dp[i-1][j-k* a[i] (k=0,1 且 j>=k*a[i[)
初始化:dp[0][0] = 0;
#include <bits/stdc++.h>
#define ll long long
#define INF 0x3f3f3f3f
using namespace std;
int a[1001];
int dp[1001][1001];
int main()
int n,k;
cin>>n>>k;
for(int i=1;i<=n;i++)
cin>>a[i];
dp[0][0] = 1;
for(int i=1;i<=n;i++)
for(int j=0;j<=k;j++)// 注意要从0开始
dp[i][j]|=dp[i-1][j];
if(j>=a[i]) dp[i][j]|=dp[i-1][j-a[i]];
// 为了和后面统一也可以写成如下的形式
// for(int k=0;k<=1&&k*a[i]<=j;k++)
// dp[i][j]|=dp[i-1][j-k*a[i]];
if(dp[n][k]) cout<<"YES";
else cout<<"NO";
return 0;
**
2.多重部分和问题
**
事实上,我们只需要在部分和代码上稍加改变就可以变成多重部分和代码;
将上述代码的状态转移方程改为dp[i][j] |= dp[i-1][j-k* a[i] (k<=m[i] 且 j>=k*a[i])即可
#include <bits/stdc++.h>
#define ll long long
#define INF 0x3f3f3f3f
using namespace std;
int a[1001];
int dp[1001][1001];
int m[1001];
int main()
int n,k;
cin>>n>>k;
for(int i=1;i<=n;i++)
cin>>a[i];
for(int i=1;i<=n;i++)
cin>>m[i];
dp[0][0] = 1;
for(int i=1;i<=n;i++)
for(int j=0;j<=k;j++)// 注意要从0开始
for(int k=0;k<=m[i]&&k*a[i]<=j;k++)
dp[i][j]|=dp[i-1][j-k*a[i]];
cout<<dp[n][k];
return 0;
**
三.变式,计算种类数
**
给定整数 a1、a2、…、an,每种数各有mi个,现在取出若干个数,使其等于k,由多少种取法?
这个问题也是一个简单的改编;
如果理解了上述的代码,很快就能发现将之前的状态转移方程的|= 改成+=即可 状态转移方程:dp[i][j] += dp[i-1][j-k* a[i] (k<=m[i] 且 j>=k*a[i])
代码请参考上一题
**
四. 在判断的基础上,输出相应的数字
**
问题 B: 新年趣事之打牌
题目描述
过年的时候,大人们最喜欢的活动,就是打牌了。xiaomengxian不会打牌,只好坐在一边看着。
这天,正当一群人打牌打得起劲的时候,突然有人喊道:“这副牌少了几张!”众人一数,果然是少了。于是这副牌的主人得意地说:“这是一幅特制的牌,我知道整副牌每一张的重量。只要我们称一下剩下的牌的总重量,就能知道少了哪些牌了。”大家都觉得这个办法不错,于是称出剩下的牌的总重量,开始计算少了哪些牌。由于数据量比较大,过了不久,大家都算得头晕了。
这时,xiaomengxian大声说:“你们看我的吧!”于是他拿出笔记本电脑,编出了一个程序,很快就把缺少的牌找了出来。
如果是你遇到了这样的情况呢?你能办到同样的事情吗?
输入
第一行一个整数TotalW,表示剩下的牌的总重量。
第二行一个整数N(1<N<=100),表示这副牌有多少张。
接下来N行,每行一个整数Wi(1<=Wi<=1000),表示每一张牌的重量。
输出
如果无解,则输出“0”;如果有多解,则输出“-1”;否则,按照升序输出丢失的牌的编号,相邻两个数之间用一个空格隔开。
样例输入
270
4
100
110
170
200
样例输出
2 4
分析发现,这就是一个部分和问题,但是题目要求输出丢失的牌子号。
我们可以引入path数组,path[j] = i,代表j这个数字可以由第i个数字(与其他数)相加形成,于是我们可以通过递归打印路径。但是事实上数据过大,递归可能炸掉,于是想到直接用栈。
(这里的代码将二维dp数组降为了一维数组)
#include <bits/stdc++.h>
#define ll long long
using namespace std;
int a[105];
int dp[100005];
int path[100005];
int deta;
int tw,n;
void print(int j)
stack<int> s;
while(j>0)
s.push(path[j]);
j-=a[path[j]];
while(!s.empty())
cout<<s.top()<<" ";
s.pop();
int main()
cin>>tw>>n;
int sum = 0;
for(int i=1;i<=n;i++)
cin>>a[i];
sum+=a[i];
deta = sum-tw;
dp[0] = 1;
for(int i=1;i<=n;i++)
for(int j=deta;j>=a[i];j--)
dp[j]+=dp[j-a[i]];
if(dp[j-a[i]]&&!path[j]) path[j] = i;//数字j可以由i组成 ,注意!path[j]这个条件
if(dp[deta]>=2) cout<<-1;
else if (dp[deta]==0) cout<<0;
else print(deta);
return 0;
**
五.例题
**
题目描述
珍珍学习乘法时,发现4=22,9=33,…,而2不可能分解为二个相同整数的乘积,但可以分解为11+11。她想知道对任意的正整数n,把它分解为几个整数与自身相乘之和,有多少种方案呢?
输入
只有一行,该行只有一个正整数n。
输出
只有一行,该行只有一个正整数,表示总方案数。
样例输入
4
样例输出
2
提示
30%的数据,1≤n≤10;
80%的数据,1≤n≤300;
100%的数据,1≤n≤800。
很简单吧!
#include <bits/stdc++.h>
#define ll long long
#define INF 0x3f3f3f3f
using namespace std;
int a[34];
int dp[1001][1001];
int main()
int n;cin>>n;
for(int i=1;i<=33;i++)
a[i] = i*i;
dp[0][0] = 1;
for(int i=1;i<=33;i++)
for(int j=0;j<=n;j++)
for(int k=0;j>=k*a[i];k++)
dp[i][j]+=dp[i-1][j-k*a[i]];
//for(int i=1;i<=n;i++) cout<<dp[i]<<" ";
cout<<dp[33][n];
return 0;
以上是关于dp 部分和问题及其扩展的主要内容,如果未能解决你的问题,请参考以下文章