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;
}