您的位置:首页 > 编程语言 > C语言/C++

C++动态规划之背包问题之多重背包求方案数之 : 新年去世(趣事)之打牌 运用递归+数组输出多重背包的路径(用了哪些物品)

2019-01-03 16:02 435 查看

此题好像不是多重背包好像是01背包怎么办。。。算了既然写都写完了就懒得改了

新年到了大家快来去世吧

#前言

新年第一篇博客!祝大家猪年大吉

此题是一道很简单的多重背包的题。相信只要有点DP基础的同学都可以打出此题的部分解。但此题的重点在于它还要要求输出在到达目标的情况下输出相应的路径。(这种方法是我自己根据以前所学琢磨出来的,可能有点纰漏,见谅。我还没有看过题解,所以知不知道是不是正解)。

话说看了看别人的题解,我的这篇博客应该算是比较详细了的吧。

题目

新年趣事去世之打牌

时间限制: 1 Sec  内存限制: 64 MB

题目描述

过年的时候,大人们最喜欢的活动,就是打牌了。xiaomengxian不会打牌,只好坐在一边看着。 这天,正当一群人打牌打得起劲的时候,突然有人喊道:“这副牌少了几张!”众人一数,果然是少了。于是这副牌的主人得意地说:“这是一幅特制的牌,我知道整副牌每一张的重量。只要我们称一下剩下的牌的总重量,就能知道少了哪些牌了。”大家都觉得这个办法不错,于是称出剩下的牌的总重量,开始计算少了哪些牌。由于数据量比较大,过了不久,大家都算得头晕了。 这时,xiaomengxian大声说:“你们看我的吧!”于是他拿出笔记本电脑,编出了一个程序,很快就把缺少的牌找了出来。 如果是你遇到了这样的情况呢?你能办到同样的事情吗?

输入

第一行一个整数TotalW,表示剩下的牌的总重量。 第二行一个整数N(1<n<=100),表示这副牌有多少张。接下来N行,每行一个整数wi(1<=wi<=1000),表示每一张牌的重量。

输出

如果无解,则输出“0”;如果有多解,则输出“-1”;否则,按照升序输出丢失的牌的编号,相邻两个数之间用一个空格隔开。

样例输入

[code]270
4
100
110
170
200

样例输出

[code]2 4

提示(笔者注)

1.此题是一个多重背包求方案数的问题。

2.此题不仅要求方案数,还要输出选了哪些卡片(输出路径)。

3.每种卡片只能选一种.

#思路

照常先解释一下样例。此题就是一个多重背包求方案数的经典问题。(如果你对这个不熟悉,可以百度一下,今后我会补充)为了方便叙述,设剩余卡片重量为W,每张卡片的重量为 ,这n张卡片加起来的总重量为 ( )。因为所有卡片的总重量为sum,而剩余卡片的重量为W,说明我们缺的卡片的重量加起来就为 。我们就要用这些 凑出(sum-W)。如果有多种方案,输出-1;如果没有方案,则输出0;如果有唯一的方案,就输出用了哪些卡片的序号.

解释完样例,我们可以先考虑不要输出用了哪些卡片,先编写出求一共有多少种方案的程序:

[code]#include <iostream>//这个代码很基础,所以简单打一下注释
#include <cstdio>
#include <cmath>
#include <cstring>
#include <queue>

using namespace std;

#define inf 0x3f
#define N 110
#define M 1010
#define LL long long
#define da double
#define mem(a,n) memset(a,n,sizeof(a));

LL read() {
LL f=1,s=0;char a=getchar();
while(!(a>='0'&&a<='9')) { if(a=='-') f=-1 ; a=getchar(); }
while(a>='0'&&a<='9') { s=s*10+a-'0'; a=getchar();}
return f*s;
}

LL n,f[M*100],w
,W,sum;//此题的f数组不能开1000,因为总重量最多有100*1000

int main() {
W=read();n=read();
for(int i=1;i<=n;i++)
w[i]=read(),sum+=w[i];
if(W==sum) {//如果本来就符合条件,直接输出1-n
for(int i=1;i<=n;i++)
cout<<i<<' ';
return 0;
}
sum=sum-W;//算出(sum-W)
if(sum<0) {//如果是负数,说明无解
cout<<"0";
return 0;
}
f[0]=1;
for(int i=1;i<=n;i++)//进行DP
for(int j=sum;j>=w[i];j--)
f[j]+=f[j-w[i]];
if(f[sum]>1) {//说明最终有多种方案,输出-1
cout<<-1;
return 0;
}
if(f[sum]==0) {//说明无解,输出0
cout<<"0";
return 0;
}
print(sum);
}

现在我们已经求解出了有多少种方案,下面就是比较棘手的输出卡片了。(这段不好想,我也想了很久)

要输出用了哪些卡片,我们首先要回归到多重背包的本质,让我们来重点看看核心代码。

[code]for(int i=1;i<=n;i++)
for(int j=sum;j>=w[i];j--)
f[j]+=f[j-w[i]];

很明显我们要在这三行代码上做文章。在上述代码上,我们用了 

[code]f[j]+=f[j-w[i]];

来进行状态转移。他的意思是:重量凑成j的方案数,要加上重量凑上j-w[i]的方案数。想到输出方案,我们就想到了前驱数组。这里同样可以使用:设前驱数组 表示为将卡片凑成i,在原来的基础上又加上了第i张卡片;那么他的上一个状态便是 ,由此便可以递归输出方案。需要注意的是,既然它的前驱是 ,则必须要保证 是合法有效的,也就是 不为0;还有,在DP中,如果arv[i]已经有了前驱,则不能再改变他的前驱。

下面是具体的代码:

[code]#include <iostream>
#include <cstdio>
#include <cmath>
#include <cstring>
#include <queue>

using namespace std;

#define inf 0x3f
#define N 110
#define M 1010
#define LL long long
#define da double
#define mem(a,n) memset(a,n,sizeof(a));

LL read() {
LL f=1,s=0;char a=getchar();
while(!(a>='0'&&a<='9')) { if(a=='-') f=-1 ; a=getchar(); }
while(a>='0'&&a<='9') { s=s*10+a-'0'; a=getchar();}
return f*s;
}

LL n,f[M*100],w
,W,sum,arv[100*M];

void print(LL p) {//递归输出
if(p<=0 || w[arv

]==0 ) return ; print(p-w[arv[p]]); cout<<arv[p]<<" "; } //deque <node> q; int main() { W=read();n=read(); for(int i=1;i<=n;i++) w[i]=read(),sum+=w[i]; if(W==sum) { for(int i=1;i<=n;i++) cout<<i<<' '; return 0; } sum=sum-W; if(sum<0) { cout<<"0"; return 0; } f[0]=1; for(int i=1;i<=n;i++) { for(int j=sum;j>=w[i];j--) { f[j]+=f[j-w[i]]; if(f[j-w[i]] && !arv[j])//记录前驱 arv[j]=i; } } if(f[sum]>1) { cout<<-1; return 0; } if(f[sum]==0) { cout<<"0"; return 0; } print(sum); }

#后记

[p]此题我错了很多遍啊。。。。。

下面我来总结一下我错的点,或许对你们有帮助

1.在print函数中,边界不仅有 ,还有 !=0,因为递归是p- ,这样会导致递归溢出;不仅有p!=0,还要有p>0,因为p<0的话就数组越界了。

2. 要不等于0才能设置为前驱;arv[j]如果已经有了前驱就不能再改变了(具体原因你们自己思考吧)

 

做了这道题,又加深了我对背包问题的理解,下次就要具体写一写背包问题了。

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: