博客园 首页 私信博主 显示目录 隐藏目录 管理 动画

洛谷.1782.旅行商的背包(背包DP 单调队列)

题目链接(卡常背包)

朴素的多重背包是: \(f[i][j] = \max\{ f[i-1][j-k*v[i]]+k*w[i] \}\),复杂度 \(O(nV*\sum num_i)\)
可以发现求\(\max\)时有很多值是被重复枚举过的

换一种方程表示形式,对于每个\(v[i]\),设\(j=K*v[i]+r,\quad K=j/v[i],\quad r=j\%v[i]\),即按照\(\%v[i]\)的余数分别进行dp(第二层枚举余数\(r\)
再枚举\(k=0\sim K-1\)(去掉\(k\)个物品\(i\)),那么 \(f[i][j] = \max\{ f[i-1][k*v[i]+r]-k*w[i] \} + K*w[i]\)
里面这一部分可以用单调队列维护。

大体是这个样子(这里\(f\)\(f[i]\)\(g\)\(f[i-1]\)\(k\)是指当前的体积为\(k\times v_i+r\)\(r\)是枚举的余数,\(v_i\)是当前物品\(i\)的单位体积,\(num_i\)\(i\)物品个数,\(val_i\)是物品\(i\)单位价值):

\[f_k=\max\limits_{k-j\leq num_i}\{g_{j}+(k-j)\times val_i\}\\f_k=\max\limits_{k-j\leq num_i}\{g_{j}-j\times val_i\}+k\times val_i \]

代码就是第一层枚举\(i\),第二层枚举\(r=0\sim v_i-1\),第三层枚举\(k\),更新\(f[i][k\times v_i+r]\),用单调队列优化。

如果你还没有看懂或想明白怎么用单调队列...
单调队列里每次存进去一个\(f[i-1][j\times v_i+r]-j\times val_i\),更新的时候是$$\begin{aligned}f[i][k\times v_i+r]&=q[h]+k\times val_i\&=\max_{k-j\leq num_i}{\ f[i-1][j\times v_i+r]-j\times val_i\ }+k\times val_i\&=\max_{k-j\leq num_i}{\ f[i-1][j\times v_i+r]+(k-j)\times val_i\ }\end{aligned}$$
\(k-j\)实际就是选了多少个物品\(i\)

复杂度是 \(O(nV)\)。二三层枚举相当于把\(0\sim V\)都跑了一遍且只有一遍。

另外二进制拆分也可做,复杂度 \(O(nV*\sum log(num_i))\)。由于数据随机下 \(num_i\)可能比较小,\(\sum log(num_i)\)是期望 \(O(n)\) 的。
而且这种方法常数更小,所以比单调队列还快。。

另一问最大数量只有5,可以直接 \(V^2\) 暴力。。

注意当前枚举的体积now与数量的限制: 当队首最优值的体积(用的数量 \(k_h\))+当前物品最大体积(个数上限\(num\))<当前枚举的体积(\(k\))时,要弹出队首
另外最后可以直接输出\(f[V]\)?我觉得不太对啊,大概是那\(m\)个物品一定会产生正权值?

卡常是真恶心==

#include <cstdio>
#include <cctype>
#include <algorithm>
#define gc() getchar()
const int N=1e4+5;

int n,m,V,f[N],q[N],nm[N];

inline int read()
{
	int now=0,f=1;register char c=gc();
	for(;!isdigit(c);c=gc()) if(c=='-') f=-1;
	for(;isdigit(c);now=now*10+c-'0',c=gc());
	return now*f;
}

int main()
{
	n=read(),m=read(),V=read();
	for(int v,w,num,now,tmp,i=1; i<=n; ++i)
	{
		v=read(),w=read(),num=read();
		for(int j=0; j<v; ++j)//r
			for(int k=0,h=1,t=0; (now=k*v+j)<=V; ++k)//now:当前体积 
			{
				tmp=f[now]-k*w;
				while(h<=t && q[t]<tmp) --t;
				nm[++t]=k, q[t]=tmp;
				if(nm[h]+num<k) ++h;//每次仅会弹出一个元素 
				f[now]=q[h]+k*w;
			}
	}
	for(int a,b,c,i=1; i<=m; ++i)
	{
		a=read(),b=read(),c=read();
		for(int j=V; j; --j)
			for(int k=0; k<=j; ++k)
				f[j]=std::max(f[j],f[j-k]+(a*k+b)*k+c);
	}
//	int res=f[1];
//	for(int i=2; i<=V; ++i) res=std::max(res,f[i]);
	printf("%d",f[V]);

	return 0;
}
posted @ 2018-02-22 16:35  SovietPower  阅读(320)  评论(0编辑  收藏  举报