测试地址:梦幻岛宝珠
做法: 本题需要用到背包DP+思维。
这道题看上去是一个裸01背包,然而容量特别大,因此我们只能从其中唯一一个特殊条件入手:
a
⋅
2
b
a\cdot 2^b
a⋅2b形式的重量。
我们考虑把这些物品分阶段来进行决策。我们首先对每个
b
b
b,求出重量表示为
a
⋅
2
b
a\cdot 2^b
a⋅2b的那些物品中,取重量为
k
⋅
2
b
k\cdot 2^b
k⋅2b的物品能得到的最大总价值
t
o
t
v
a
l
(
b
,
k
)
totval(b,k)
totval(b,k),这就是一个一般的01背包了,因为
a
≤
10
,
n
≤
100
a\le 10,n\le 100
a≤10,n≤100,所以时间复杂度最多是
1
0
6
10^6
106的级别。接下来,我们从低位向高位DP,令
f
(
i
,
j
)
f(i,j)
f(i,j)为取用
b
≤
i
b\le i
b≤i的物品,容量为
j
⋅
2
i
j\cdot 2^i
j⋅2i加上
W
W
W在第
i
i
i位以下的所有容量时所能得到的最大总价值。上面
W
W
W在第
i
i
i位以下的容量,就指的是它在第
i
i
i位以下的上界(用位运算解释就是
W
&
(
(
1
<
<
i
)
−
1
)
W\&((1<<i)-1)
W&((1<<i)−1))。在转移的时候,我们首先枚举当前的
j
j
j,然后看前一位有哪些可以转移到当前状态的状态。显然,由于要满足第
i
i
i位之前是上界的条件,第
i
−
1
i-1
i−1位应该和
W
W
W的第
i
−
1
i-1
i−1位相同,这才能转移。于是我们就写出来了一个状态转移的式子,就解决了这道题,时间复杂度虽然看上去有点大,但均摊下来还是跑得很快的。
以下是本人代码:
#include <bits/stdc++.h>
using namespace std;
int n,W,totw[35],f[35][2100],len;
vector<int> w[35],val[35];
int main()
{
while(scanf("%d%d",&n,&W)&&n>=0&&W>=0)
{
len=0;
for(int i=0;i<35;i++)
w[i].clear(),val[i].clear();
memset(totw,0,sizeof(totw));
memset(f,0,sizeof(f));
for(int i=1;i<=n;i++)
{
int x,y;
scanf("%d%d",&x,&y);
int j=0;
while(!(x&1)) {j++;x>>=1;}
w[j].push_back(x);
totw[j]+=x;
val[j].push_back(y);
len=max(len,j);
}
while(W>>len) len++;
len--;
for(int i=0;i<=len;i++)
for(int j=0;j<(int)w[i].size();j++)
for(int k=totw[i];k>=w[i][j];k--)
f[i][k]=max(f[i][k],f[i][k-w[i][j]]+val[i][j]);
for(int i=1;i<=len;i++)
{
totw[i]+=((totw[i-1]+1)>>1);
for(int j=totw[i];j>=0;j--)
for(int k=0;k<=j;k++)
f[i][j]=max(f[i][j],f[i][j-k]+f[i-1][min(totw[i-1],(k<<1)|((W>>(i-1))&1))]);
}
printf("%d\n",f[len][1]);
}
return 0;
}