Div2 C. Knapsack

C. Knapsack

题意
一个体积为W的背包,有N个物品,每一个物体有一个重量,问放那几个物体可以使的背包中的重量在[(W-1)/2+1,W]之间。
思路
这一题在昨天晚上写的时候,队友想出是一个贪心,然后交上去AC了,但是证明的时候却发现没法证明这个贪心的正确性,今天证明一下。
首先对所有物品从小到大排序。
如果有物品的重量大于W,就直接跳过。

如果有一个物品的重量在[(W-1)/2+1,W]之间,那就直接把这个物品放入背包,然后就结束了。

如果没有在[(W-1)/2+1,W]之间的物品,那能选的物品就只剩下都小于[(W-1)/2+1,W]了,然后从大到小依次加在一起(其实并不用按从大到小的顺序加,按从小到大的顺序也可以,顺序没有要求,可以随便挑都可以。),看是否能出现在[(W-1)/2+1,W]的值了,如果所有数都加一起都不够(W-1)/2+1,就输出-1,否则就输出刚好满足在区间内的值。
证明
我们是从小于(W-1)/2+1的数中挑选,所以说任意两个数的和肯定都小于W。
如果两个小于(W-1)/2+1的数相加以后,如果不在(W-1)/2+1到W的范围中,那这两个数的和一定小于(W-1)/2+1
那么我们可以在小于(W-1)/2+1的数中再挑一个数和刚才的和加在一起,肯定也是小于W的,因为刚才已经可以证明那两个数的和是小于(W-1)/2+1的。
所以我们可以随便挑两个数相加,只要两个数不在[(W-1)/2+1,W]这个范围内,那么这两个数就一定小于(W-1)/2+1,我们可以继续找一个数相加,判断他们是否在[(W-1)/2+1,W],如果不在,那他们三个数的和就一定小于(W-1)/2+1,依次相加,最终要么所有数的和小于(W-1)/2+1,要么一定会出现一个在[(W-1)/2+1,W]的数。
代码

#include<iostream>
#include<algorithm>
using namespace std;
const int N=2e5+10;
typedef long long ll;
struct Node
{
    ll x,y;
}a[N];
bool cmp(Node a1,Node a2)
{
    return a1.x<a2.x;
}
int main()
{
    int t;
    cin>>t;
    while(t--)
    {
        int n;
        ll w;
        cin>>n>>w;
        for(int i=1 ; i<=n ; i++)
        {
            cin>>a[i].x;
            a[i].y=i;
        }
        sort(a+1,a+1+n,cmp);
        int flag=0;
        bool k=false;
        for(int i=n ; i>=1 ; i--)
        {
            if(a[i].x<=w&&a[i].x>=((w-1)/2+1))
            {
                cout<<"1"<<endl;
                cout<<a[i].y<<endl;
                k=true;
                break;
            }
            if(a[i].x<((w-1)/2+1))
            {
                flag=i;
                break;
            }
        }
        if(k) continue;
        if(flag==0)
        {
            cout<<"-1"<<endl;
            continue;
        }
        ll ans=0;
        bool s=false;
        for(int i=flag ; i>=1 ; i--)
        {
            ans+=a[i].x;
            if(ans>=((w-1)/2+1)&&ans<=w)
            {
                cout<<flag-i+1<<endl;
                for(int j=i ; j<=flag ; j++)
                {
                    cout<<a[j].y<<" ";
                }
                cout<<endl;
                s=true;
                break;
            }
        }
        if(!s)
        {
            cout<<"-1"<<endl;
        }
    }
    return 0;
}
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值