【BZOJ1190】[HNOI2007] 梦幻岛宝珠(DP水题)

点此看题面

大致题意: 01背包问题。其中物品个数(n≤100),背包容量和每个物品体积(≤2^{30}),且每个物品体积可以表示为(a imes 2^b(1le ale 10,ble 30))

前言

大概是我天天念叨总是做不出(DP)题,结果就撞见这样一道水题。

可是,一道水题都要想好久,还挂了几发,我毕竟还是太菜。

大致思路

考虑题目涉及这么多(2)的幂,自然而然就想到从高到低枚举每一位(DP)

我们假设当前枚举到第(i)位,则我们强制所有体积为(a imes 2^i)的物品都要在此时判断是否选择。

即,我们假设(DP)到第(i)位我们最多能选体积为(j imes 2^i)的物品(此时忽略末(i)位),那么原本的大背包就被我们转化为对于每一位的小背包。

那么这样有什么好处呢?

由于(ale10)的,所以我们可以把(j>10n)时的情况全部归于(j=10n)!(显然吧,因为多余的容量反正用不完,不如直接舍弃)

大致整理下思路:先枚举(i),然后通过小背包求出(g_k)表示选择体积不超过(k imes 2^i)的物品所能得到的最大代价,再枚举(j)通过刷表法(推荐使用,因为要舍弃多余容量)枚举一个(k)转移(f_{i,j})

然后算算复杂度,枚举(i)(O(logV)),而刷表法转移(f_{i,j})(O(100n^2))的。(小背包是针对每个物品的,复杂度为(O(10n^2)),此处可以忽略)

总复杂度是(O(100n^2logV)),看似会(T),但实际上肯定是跑不满的。就连BZOJ上都跑得飞快,你还虚什么!

代码

#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg 
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 100
#define pb push_back
#define Gmax(x,y) (x<(y)&&(x=(y)))
using namespace std;
int n,lim,p[35],f[35][N*10+5],g[N*10+5];
struct data {int a,v;I data(CI x=0,CI y=0):a(x),v(y){}};vector<data> V[35];
int main()
{
	RI i,j,k,x,y,t,len,ans;vector<data>::iterator it;W(scanf("%d%d",&n,&x),~n&&~x)
	{
		len=-1;W(x) p[++len]=x&1,x>>=1;for(i=0;i<=len;++i) V[i].clear();//把背包容量转化为二进制,并清空vector
		for(i=1;i<=n;++i) {scanf("%d%d",&x,&y),t=0;W(!(x&1)) x>>=1,++t;V[t].pb(data(x,y));}//转化为a*2^b的形式,存入vector
		for(lim=10*n,i=0;i<=len;++i) for(j=0;j<=lim;++j) f[i][j]=-1;//初始化清空
		for(f[len][1]=ans=0,i=len;~i;--i)//枚举每一位
		{
			for(j=0;j<=lim;++j) g[j]=0;//清空
			for(it=V[i].begin();it!=V[i].end();++it)//枚举这一位上的物品
				for(j=lim;j>=it->a;--j) Gmax(g[j],g[j-it->a]+it->v);//小背包
			for(j=0;j<=lim;++j) for(k=0;k<=j;++k) ~f[i][j]&&//枚举j,k刷表
				(i&&Gmax(f[i-1][min(j-k<<1|p[i-1],lim)],f[i][j]+g[k]),Gmax(ans,f[i][j]+g[k]));//注意i=0时不再转移
		}printf("%d
",ans);//输出答案
	}return 0;
}
原文地址:https://www.cnblogs.com/chenxiaoran666/p/BZOJ1190.html