题目大意:每个物品有自己的体积和价值,要求体积xi不超过m的情况下获得的最大价值W,(w,x特别大)
输入:
第一行两个整数n,m。
接下来n行每行两个整数,xi,wi,表示第i个物品的体积和价值。
2≤n≤40
0≤m≤10^18
0≤xi,wi≤10^15
输出:
一行一个整数表示最大价值。
分析:
这题的体积x和价值w的范围非常大所以用01背包的板子数组开不下,看到n非常小,考虑dfs,但是n最大40,2^40也比较大,dfs一般1e6多会爆,2的20次是1,048,576刚好1e6多一点,可以分两次来做。
第一遍dfs把前n/2个物品的选和不选的情况枚举出来,存到一个结构体数组o中,o里面存体积x以及x下获得的价值
然后扫一遍o[i].w = max(o[i].w,w[i-1].w)使得第二遍二分搜索m-sumx的价值是最大价值
第二遍dfs也像第一遍一样枚举选和不选,在退出时,二分搜索第一个小于等于m-sumx体积的最大价值+当前的sumw,更新ans最大值。
#include<bits/stdc++.h>
using namespace std;
using i64 = long long;
using i128 = __int128;
#define ios ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
template <typename T>
inline T read() {
T sum = 0, fl = 1;
char ch = getchar();
for (; !isdigit(ch); ch = getchar())
if (ch == '-') fl = -1;
for (; isdigit(ch); ch = getchar())
sum = sum * 10 + (ch - '0');
return sum * fl;
}
template <typename T>
inline void write(T x) {
static int sta[45];
int cnt = 0;
if (x < 0) putchar('-'), x = -x;
do {
sta[cnt++] = x % 10, x /= 10;
} while (x);
while (cnt) putchar(sta[--cnt] + '0');
}
const int maxn = 1e6+10;
int n;
i64 m;
i64 ans,x[50],w[50];
struct ty{
i64 x,w;
bool operator<(const ty &a)const{
return x<a.x;
}
}o[maxn<<2];
int tot;
void dfs1(int cnt,i64 sumx,i64 sumw){
if(sumx>m) return;
if(cnt==n/2+1){
o[++tot]={sumx,sumw};
return;
}
dfs1(cnt+1,sumx,sumw);
dfs1(cnt+1,sumx+x[cnt],sumw+w[cnt]);
}
void dfs2(int cnt,i64 sumx,i64 sumw){
if(sumx>m) return;
if(cnt==n/2){
int pos = upper_bound(o+1,o+tot+1,ty{m-sumx,sumw})-o-1;//第一个小于等于的元素
ans = max(ans,o[pos].w+sumw);
return;
}
dfs2(cnt-1,sumx,sumw);
dfs2(cnt-1,sumx+x[cnt],sumw+w[cnt]);
}
int main(){
n = read<int>();
m = read<i64>();
for(int i = 1;i<=n;++i){
x[i] = read<i64>();
w[i] = read<i64>();
}
dfs1(1,0,0);
sort(o+1,o+tot+1);
for(int i = 1;i<=tot;++i){
o[i].w = max(o[i].w,o[i-1].w);
}
dfs2(n,0,0);
write<i64>(ans);
return 0;
}
c++STL upper_bound()返回第一个大于的元素的索引,再减一就是小于等于了,结构体的二分我也学到了,手搓也不是不行就是容易写错