【BZOJ1190】[HNOI2007] 梦幻岛宝珠(DP水题)
大致题意: 01背包问题。其中物品个数\(n≤100\),背包容量和每个物品体积\(≤2^{30}\),且每个物品体积可以表示为\(a\times 2^b(1\le a\le 10,b\le 30)\)。
前言
大概是我天天念叨总是做不出\(DP\)题,结果就撞见这样一道水题。
可是,一道水题都要想好久,还挂了几发,我毕竟还是太菜。
大致思路
考虑题目涉及这么多\(2\)的幂,自然而然就想到从高到低枚举每一位\(DP\)。
我们假设当前枚举到第\(i\)位,则我们强制所有体积为\(a\times 2^i\)的物品都要在此时判断是否选择。
即,我们假设\(DP\)到第\(i\)位我们最多能选体积为\(j\times 2^i\)的物品(此时忽略末\(i\)位),那么原本的大背包就被我们转化为对于每一位的小背包。
那么这样有什么好处呢?
由于\(a\le10\)的,所以我们可以把\(j>10n\)时的情况全部归于\(j=10n\)!(显然吧,因为多余的容量反正用不完,不如直接舍弃)
大致整理下思路:先枚举\(i\),然后通过小背包求出\(g_k\)表示选择体积不超过\(k\times 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\n",ans);//输出答案
}return 0;
}
待到再迷茫时回头望,所有脚印会发出光芒