超大背包问题 二分枚举 pair数组应用 位运算表示集合 超大背包问题 二分枚举 pair数组应用 位运算表示集合
关键词
二分枚举 pair数组应用 位运算表示集合 二分搜索
笺释
我觉得这道题真是很好很好,设计到的三个知识点我只对位运算熟悉一点,其他两个的话于我而言都感觉又多明白了一点,算法科学真美啊。
因为开不出来这么大的背包,但是n比较小,于是考虑用枚举,但是枚举的话2n会超时(顺便一提2n应该是并不会超内存的,最大用ll开到过n=50的情况)。所以二分枚举就好了。
至于为什么能够二分枚举
我认为能二分枚举的题目都有这样的特征分成两半后,带着前一半的数据取处理后一半的数据能够得到最终数据。,我想说的并不是这种可以分成两半的结构还是可以归并的结构,我觉得更有意思的是这很像是二分答案不是吗,带着已知的一部分答案去查找另一部分。
然后一个很厉害的地方就是对pair组的应用
在做这道题之前一直以为pair数组不就是map吗,他能做的map都能做。现在完全不这样想了,也可能是我太困了脑筋转不动了,但是我觉得这道题把数据结构换成map是没办法做的。
先是去掉了pair数组中重量大但是价值低的物品,一定是不拿的。
这时候数组中任意一个都满足,重量大,价值一定也大。
我们要找到第二部分里能取的(w小于W-w1)里v最大的只需要找到i最大的,他的v一定最大。
还要注意lowerbond的用法
找到的是最小的大于等于W-w1的嘛,那前一个就是我们能取的最大的了,因为pair组是按照W排序的。
但是注意这道题make_pair()第二个参数只能是INF,至于为什么,我目前还不太理解。
完整代码(未严格测试)
#include<bits/stdc++.h>
#define MAXN 45
const long long INF =0x3f3f3f3f;
using namespace std;
int n;
long long w[MAXN],v[MAXN];
long long W;
pair<long long,long long>ps[1<<MAXN/2];
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
scanf("%lld",&w[i]);
}
for(int i=1;i<=n;i++)
{
scanf("%lld",&v[i]);
}
scanf("%lld",&W);
int n2=n/2;
for(int i=0;i<=(1<<n2)-1;i++)
{
long long vv=0,ww=0;
for(int j=1;j<=n2;j++)
{
if(i>>(j-1)&1)
{
// printf("ss
");
vv+=v[j];
ww+=w[j];
}
}
//按照w排序才行
ps[i]=make_pair(ww,vv);
}
//
sort(ps,ps+(1<<n2));
int m=1;
//从0到m-1按照pair增序
for(int i=1;i<=(1<<n2)-1;i++)
{
if(ps[m-1].second<ps[i].second)
{
ps[m++]=ps[i];
}
}
int sn2=n-n2;
long long ans=0;
for(int i=0;i<=(1<<sn2)-1;i++)
{
long long vv=0,ww=0;
for(int j=1;j<=sn2;j++)
{
if(i>>(j-1)&1)
{
vv+=v[j+n2];
ww+=w[j+n2];
}
}
//按照w排序才行
if(ww<=W)
{
long long tv=(lower_bound(ps,ps+m,make_pair(W-ww,INF))-1)->second;
ans=max(ans,tv+vv);
}
}
printf("%lld
",ans);
}
//4 2 1 3 2 3 2 4 2 5