极大容量的完全背包问题
题目大意:
就是题目名称的意思,有n种物品,一个容量为m的背包,每种物品的体积为$ a_i $,价值为$ b_i $,有$ n<=10^6,m<=10^{18},a_i,b_i<=100 $。求最大价值。
解题方法:
因为m很大,所以我们考虑将较大的体积为S的背包分为较小的两个背包,其中一个体积为x,则另一个S-x。
这时讨论abs(x-(S-x))的范围,可以推出其<=maxv(物品的最大体积)。因为单独对两个背包操作,两边会有余留下来的部分,将其中一个余留下来的部分补到另一个去可能会得到更忧解。但如果移动的部分大于maxv了则放在两个背包中的任意一个都可以得到最优解。所以我们成功将枚举区间缩小到了$
\frac{S-maxv} {2} <=x<= \frac{S+maxv} {2} $。
然后我们类似分治的套路将图画出来:
发现$ Max -Min < 2\times maxv $并且最底层(即$ Min>0 $的最后一层)的$ Min <= maxv,Max<=3 \times maxv $,所以我们可以预处理出$ 3\times maxv $大小的背包就可以得到最底层的解,然后我们可以记录每层最小值$ L[i] $即最左边的点的权值,设$ g[i][j] $表示在第i层时比$ L[i] $容量大j的背包的最优解,枚举x逐层向上转移即可,转移方程:$ g[i][v-L[i]]=max(g[i+1][x-L[i+1]]+g[i+1][v-x-L[i+1]]) $。
因为一开始给了$ 10 ^ 6$种物品,然而因为最多有$ 100\times 100 $种物品,所以可以用set去重。
代码:
1 #include<bits/stdc++.h> 2 #define ll long long 3 #define del(a,b) memset(a,b,sizeof(a)) 4 using namespace std; 5 const int MAXN=1e5+10; 6 ll n,m,tot; 7 set<int> ap[110]; 8 ll f[MAXN],g[55][MAXN],L[55],a[MAXN],b[MAXN]; 9 void Max(ll &a,ll b){a=(a>b? a:b);} 10 template <class T>void read(T &x) 11 { 12 bool f=0;char ch=getchar();x=0; 13 for(;ch<'0' || ch>'9';ch=getchar())if(ch=='-') f=1; 14 for(;ch>='0' && ch<='9';ch=getchar())x=x*10+ch-'0'; 15 if(f) x=-x; 16 } 17 int main() 18 { 19 read(n);read(m); 20 ll maxv=0; 21 for(int i=1;i<=n;i++) 22 { 23 ll x,y;read(x);read(y); 24 if(ap[x].find(y)!=ap[x].end()) continue; 25 a[++tot]=x;b[tot]=y;ap[x].insert(y); 26 Max(maxv,a[tot]); 27 } 28 ll s=m,cnt=0; 29 while(s>0) 30 { 31 L[++cnt]=s; 32 s=(s-maxv)>>1; 33 } 34 for(int i=1;i<=tot;i++) 35 for(int v=a[i];v<=maxv*3;v++) 36 Max(f[v],f[v-a[i]]+b[i]); 37 for(int i=cnt;i>=1;i--) 38 for(ll v=L[i];v<=L[i]+maxv*2;v++) 39 { 40 if(v<=maxv*3) g[i][v-L[i]]=f[v]; 41 else 42 { 43 for(ll x=(v-maxv)>>1;x<=v>>1;x++) 44 Max(g[i][v-L[i]],g[i+1][x-L[i+1]]+g[i+1][v-x-L[i+1]]); 45 } 46 } 47 printf("%lld",g[1][0]); 48 return 0; 49 }