1.题目
困难版的输入:
输入样例:
3 1
7 3
10 7
9 6
输出样例:
2
注:本题的easy(简单版),与hard(困难版)唯一的不同之处只有数据范围。
2.解题思路+代码:
我们可以发现本题虽然和01背包很像,但是却不能用01背包来做,这个题只能枚举答案,hard版本质是贪心,依次枚举判断高位的情况。
当然也可以直接干hard版,不过easy版的思路还是很重要,因为很多时候我们就是要枚举答案,枚举答案就是正解,比如二分枚举答案等,不过这题是不能二分的,没有单调性
easy版:
思路:
我们可以发现最大价值之有2000,我们可以从后往前枚举,当有一个符合而条件,输出即可。
难点是如何判断该价值是否符合条件。
这里我们首先定义了一个体积,是1<<11,因为log(2000)就是10.多所以至少11位。当然比11大也没问题,因为第一次&就会变成你所选第一个物品的体积(具体原因同下,和&运算有关)
然后我们从1-n依次枚举每个物品,判断条件: (w[j]&i) == i,试想起初i(价值)是比较大的,那么&上一个小的,必然不是小的,跳过,只有在遇上>=所枚举的价值的时候才会成立(因为&上一个数要么变小,要么不变), 因此我们是&上了每个符合条件的大于等于当前价值的物品,这样既能使物品价值不变,还能压缩物品体积。因此我们也可以看出,最大可能的价值就是,max(w[i]),因此也不一定非从2000开始,从max(w[i])开始可以过hard更多数据。
对于大数而言,例如2,&完以后是会发生变化的,因此大的数我们还得判断是否符合条件,即让当前枚举价值不变。
代码:
#include<iostream>
using namespace std;
const int N=2e3+10;
int v[N],w[N];
int main()
{
int n,k;
cin>>n>>k;
for(int i=1;i<=n;i++)
{
cin>>v[i]>>w[i];
}
int ans=0;
for(int i=2000;i>=0;i--)
{
int v_sum=(1<<11)-1;//至少右移11位,解释见上
for(int j=1;j<=n;j++)
{
if( (w[j]&i) == i) v_sum&=v[j];
//如果最大值不到i,&下来绝对不等于i
//肯定是按价值由大到小开始枚举,然后判断&完价值是否不变,来压缩体积
}
if(v_sum<=k||i==0)//答案有可能是0,到0了就输出就行
{cout<<i;return 0;}
}
return 0;
}
hard版:
如果用上面的枚举过了83%的数据,对于蓝桥杯啥的肯定够用了。下面来说一种位运算优化版本的。
思路:
hard版的数据范围来到了10^9,log (10 ^ 9)大概是29.8,至少<<30位。当然多了肯定没问题,&一下就没了。
价值最大是10^9,又是&操作,因此我们用int完全可以,不一定是hard就开long long。
这题和上次不同,我们采取从高位开始利用二进制枚举答案的每一位。
我们首先value采用的是 | 操作,即除了当前位置,不会改变ans的其他位置的值。
当我们没有枚举到合适高位的时候,其实是这样的过程:
我们上面说过,&比当前价值大的可以,因此当我们100…00全是0都不可以,那么后面有1的就更不行了(因为价值更大了),而easy版的枚举需要枚举每个位置的状态,hard也就是在这里做出了优化
当我们枚举到一个可以的高位的时候,我们会把它记录到ans中,因为每次是|操作,所以我们能把高位保留下来,继续去判断下面的高位行不行。
代码:
#include<iostream>
using namespace std;
const int N=2e5+10;
int v[N],w[N];
int main()
{
int n,k;
cin>>n>>k;
for(int i=1;i<=n;i++) cin>>v[i]>>w[i];
int ans=0;
for(int i=30;i>=0;i--) //log(10^9)=29.8... 所以至少取30
{
int value=ans|(1<<i);
int v_sum=(1<<31)-1;
for(int j=1;j<=n;j++)
{
if( (w[j]&value) == value ) v_sum &= v[j];
}
if(v_sum<=k) ans=value;
}
cout<<ans;
return 0;
}
呜呜呜,看了两三个小时才看懂,给个赞叭~ ~ ~