Vijos-1071 (背包)

题目

Description

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

Input

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

Output

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

Sample Input

270
4
100
110
170
200

Sample Output

2 4

HINT

各个测试点1s

分析

  • 其实是道简单的 01背包 问题,只是需要你把具体的方案写出来罢了。
  • 开始我有一个暴力的想法,就是把每个状态的方案都记录下来(要是多解的话就不理它了),想了想用了个 bitset 记这种状态每张牌是否要选,竟然过了,不过空间是极大的。
  • 后来想了想,用了个类似链表的思想,把到达这个状态要选的最后一张牌记下,那么最后要是确定解唯一,就可以倒过来推出所有要选的牌了,写出来,空间和时间果然都优化了不少。
  • 有一点注意,它是最多100张牌,每张牌最重1000,所以有100000种状态。

代码

第一种方法的代码

#include <cstdio>
#include <bitset>
using namespace std;
int W,N,w,s,f[100010];
bitset <105> g[100010];

int main(){
    f[0]=1;
    scanf("%d%d",&W,&N);
    for (int i=1; i<=N; i++){
        scanf("%d",&w);
        s+=w;
        for (int j=s; j>=w; j--){
            if (f[j-w]==1){
                if (f[j]==0) f[j]=1,g[j]=g[j-w],g[j][i]=1;
                else f[j]++;
            }
            if (f[j-w]>1) f[j]=3;
        }
    }
    if(f[W]>1) printf("-1");
    else if (f[W]==0)printf("0");
    if (f[W]==1) for (int i=1; i<=N; i++) if (!g[W][i]) printf("%d ",i);
    return 0;
}

第二种方法的代码

#include <cstdio>
int W,N,s,w[105],f[100005],e[100005];

int main(){
    f[0]=1;
    scanf("%d%d",&W,&N);
    for (int i=1; i<=N; i++) scanf("%d",&w[i]);
    for (int i=(s=w[N],N); i; i--,s+=w[i]){
        for (int j=s; j>=w[i]; j--) if (f[j-w[i]]){
            if (f[j-w[i]]==1) if (f[j]==0) f[j]=1,e[j]=i; else f[j]=2;
            if (f[j-w[i]]>1) f[j]=2;
        }
    }
    s-=W;
    if (f[s]==0) printf("0");
    if (f[s]==1) for (int i=s; i; i-=w[e[i]]) printf("%d ",e[i]);
    if (f[s]>1) printf("-1");
    return 0;
}

提示

给出两种方法的评测结果:

#总耗时峰值内存
159ms2.32MiB
222ms928.0KiB
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值