洛谷传送门
BZOJ传送门
题解:
我们发现这就是裸的01背包,但是数据很大。。
于是可以模拟退火了。
题目给了一个限制,除掉2的若干次幂后剩余的数狠小,考虑从这里入手优化。
很显然的,我们可以先给所有除掉二的次数相同的物品做一次背包。
令 f [ i ] [ j ] f[i][j] f[i][j]表示体积为 j ∗ 2 i j*2^i j∗2i,现在只装了 k ∗ 2 i k*2^i k∗2i的物品的最大价值。
现在考虑合并。
发现一个问题:在高位的转移的时候,低位已经不可能变了,也就是不存在进位了。
我们直接在 f f f数组上做转移,现在令 f [ i ] [ j ] f[i][j] f[i][j],表示选择前 i i i组的物品,总代价不超过 j ∗ 2 i j*2^i j∗2i的最大值。
发现这个可以非常方便地转移了,因为没有进位。
f [ i ] [ j ] = max ( f [ i ] [ j ] , f [ i ] [ j − k ] + f [ i − 1 ] [ min ( w [ i − 1 ] , ( k < < 1 ) ∣ ( ( m > > ( i − 1 ) ) & 1 ) ) ] ) f[i][j]=\max(f[i][j],f[i][j-k]+f[i-1][\min(w[i-1],(k<<1)|((m>>(i-1))\&1))]) f[i][j]=max(f[i][j],f[i][j−k]+f[i−1][min(w[i−1],(k<<1)∣((m>>(i−1))&1))])
然后最后在最高位取一个 f [ l e n ] [ 1 ] f[len][1] f[len][1]就行了,由 f f f的定义显然。
代码:
#include<bits/stdc++.h>
#define ll long long
#define re register
#define cs const
using std::cerr;
using std::cout;
int n,m;
int len;
int f[35][1100],w[35];
std::vector<int> W[35],val[35];
inline void solve(){
for(int re i=1;i<=n;++i){
int x,y;
scanf("%d%d",&x,&y);
int j=__builtin_ctz(x);x>>=j;
W[j].push_back(x);w[j]+=x;
val[j].push_back(y);
len=std::max(len,j);
}
for(int re i=0;i<=len;++i){
for(int re j=0;j<W[i].size();++j)
for(int re k=w[i];k>=W[i][j];--k)
f[i][k]=std::max(f[i][k],f[i][k-W[i][j]]+val[i][j]);
}
while(m>>len)++len;--len;
for(int re i=1;i<=len;++i){
w[i]+=(w[i-1]+1)/2;
for(int re j=w[i];~j;--j)
for(int re k=0;k<=j;++k)
f[i][j]=std::max(f[i][j],f[i][j-k]+f[i-1][std::min(w[i-1],(k<<1)|((m>>i-1)&1))]);
}
cout<<f[len][1]<<"\n";
}
inline void init(){
for(int re i=0;i<35;++i)W[i].clear(),val[i].clear();
memset(f,0,sizeof f);
memset(w,0,sizeof w);
len=0;
}
signed main(){
// freopen("island.in","r",stdin);
while(~scanf("%d%d",&n,&m)&&n!=-1)init(),solve();
return 0;
}