正题
题目链接:https://www.luogu.com.cn/problem/P3188
题目大意
nnn个物品,大小为WWW的背包。
每个物品的大小可以表示为wi=ai2biw_i=a_i2^{b_i}wi=ai2bi,有价值viv_ivi。
求选择一些物品不超过背包的大小使得价值最大。
解题思路
设先计算bib_ibi相同的物品,设fi,jf_{i,j}fi,j表示只计算b=ib=ib=i的物品时,一个容量为j∗2ij*2^{i}j∗2i时的背包时的最大价值。
然后将多个背包合并,设gi,jg_{i,j}gi,j表示只计算b≤ib\leq ib≤i的物品时,容量为j∗2i+W&(2i−1)j*2^i+W\&(2^i-1)j∗2i+W&(2i−1)时的最大价值。
然后有转移方程gi,j=max{fi,j−k+gi−1,2k+(W>>(i−1))&1}g_{i,j}=max\{\ \ f_{i,j-k}+g_{i-1,2k+(W>>(i-1))\&1}\ \ \}gi,j=max{ fi,j−k+gi−1,2k+(W>>(i−1))&1 }
然后答案就是g30,0g_{30,0}g30,0
时间复杂度O(nloga∑a)O(n\log a\sum a)O(nloga∑a)
codecodecode
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
using namespace std;
const int N=110;
int n,W,a[N],v[N],lim[N];
int f[N][N*N],g[N][N*N];
vector<int> q[N];
int main()
{while(1){ memset(f,0,sizeof(f));memset(g,0,sizeof(g));memset(lim,0,sizeof(lim));for(int i=0;i<=30;i++)q[i].clear(); scanf("%d%d",&n,&W);if(n==-1)return 0;for(int i=1;i<=n;i++){int b=0;scanf("%d%d",&a[i],&v[i]);while(a[i]>10 || !(a[i]&1))b++,a[i]/=2;q[b].push_back(i);lim[b]+=a[i];}for(int i=0;i<=30;i++)for(int j=0;j<q[i].size();j++)for(int k=lim[i];k>=a[q[i][j]];k--)f[i][k]=max(f[i][k-a[q[i][j]]]+v[q[i][j]],f[i][k]);for(int i=0;i<=lim[0];i++)g[0][i]=f[0][i];for(int i=1;i<=30;i++){lim[i]+=(lim[i-1]+1)/2;for(int j=0;j<=lim[i];j++)for(int k=0;k<=j;k++)g[i][j]=max(g[i][j],f[i][j-k]+g[i-1][min(lim[i-1],2*k+((W>>i-1)&1))]);}printf("%d\n",g[30][0]);}
}