【HNOI2007】【洛谷P3188】【BZOJ1190】梦幻岛宝珠(01背包)(DP套DP)

洛谷传送门

BZOJ传送门


题解:

我们发现这就是裸的01背包,但是数据很大。。

于是可以模拟退火了。

题目给了一个限制,除掉2的若干次幂后剩余的数狠小,考虑从这里入手优化。

很显然的,我们可以先给所有除掉二的次数相同的物品做一次背包。

f [ i ] [ j ] f[i][j] f[i][j]表示体积为 j ∗ 2 i j*2^i j2i,现在只装了 k ∗ 2 i k*2^i k2i的物品的最大价值。

现在考虑合并。

发现一个问题:在高位的转移的时候,低位已经不可能变了,也就是不存在进位了。

我们直接在 f f f数组上做转移,现在令 f [ i ] [ j ] f[i][j] f[i][j],表示选择前 i i i组的物品,总代价不超过 j ∗ 2 i j*2^i j2i的最大值。

发现这个可以非常方便地转移了,因为没有进位。

f [ i ] [ j ] = max ⁡ ( f [ i ] [ j ] , f [ i ] [ j − k ] + f [ i − 1 ] [ min ⁡ ( w [ i − 1 ] , ( k &lt; &lt; 1 ) ∣ ( ( m &gt; &gt; ( i − 1 ) ) &amp; 1 ) ) ] ) f[i][j]=\max(f[i][j],f[i][j-k]+f[i-1][\min(w[i-1],(k&lt;&lt;1)|((m&gt;&gt;(i-1))\&amp;1))]) f[i][j]=max(f[i][j],f[i][jk]+f[i1][min(w[i1],(k<<1)((m>>(i1))&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;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值