hdu2191多重背包单调队列优化

http://acm.hdu.edu.cn/showproblem.php?pid=2191
模板,果然dp还是要从零开始补/kk

朴素方法,用 (f(i,j)) 表示考虑前 (i) 个物品,(j) 元钱最多能获得多少价值,下面用 (n) 表示物品种类数,(m) 表示总钱数
(f(i,j)=max(f(i-1,j-kcdot w_i)+kcdot v_i),kle num_i)

换一种形式,因为是 (kcdot w_i),所以尝试考虑以下 (j) 除以 (w_i) 的余数,也就是令 (d=jmod w_i)
(f(i,j)=max(f(i-1,d+kcdot w_i)+(num_i-k)cdot v_i))
其实就是选了 (num_i-k) 个这种物品,再整理一下就是
(f(i,j)=max(f(i-1,d+kcdot w_i)-kcdot v_i)+num_icdot v_i)

所以就可以对于每一个物品,枚举每个 (d),用单调队列维护一下
对于每个 (d),枚举 (k) 满足 (d+kcdot w_ile m)
单调队列操作:然后把 (f(i-1,d+w_icdot k)) 入队同时弹出所有比他小的,把所以的 (k',k-k'<num_i) 都出队,因为它们已经不满足此物品个数的要求
用单调队列的性质取一个最大值和 (f(i,d+kcdot w_i)) 来去 (max) 就行了

(dle w-1,kle dfrac{m-d}{w})
所以没做一次单调队列的循环次数就是

[sum_{d=0}^{w-1}dfrac{m-d}{w}=dfrac{sum_{d=0}^{w-1}m-d}{w}=dfrac{mw-sum_{d=0}^{w-1}}{w}=dfrac{w(m-0.5w+1)}{w}=m-0.5w+1 ]

也就是 (O(m)),希望没有假,在复杂度这还卡了一会
所以总复杂度是 (O(nm))

#include<cstdio>
#include<algorithm>
#include<iostream>
#include<cmath>
#include<map>
#include<iomanip>
#include<cstring>
#define reg register
#define EN std::puts("")
#define LL long long
inline int read(){
	register int x=0;register int y=1;
	register char c=std::getchar();
	while(c<'0'||c>'9'){if(c=='-') y=0;c=std::getchar();}
	while(c>='0'&&c<='9'){x=x*10+(c^48);c=std::getchar();}
	return y?x:-x;
}
int f[105][105];
int num[2005],ind[2005],tail,head;
int n,m;
int main(){int T=read();while(T--){
	std::memset(f,0,sizeof f);
	m=read();n=read();
	for(reg int w,v,tot,i=1;i<=n;i++){
		w=read();v=read();tot=read();
		for(reg int j=1;j<=m;j++) f[i][j]=f[i-1][j];
		for(reg int d=0;d<w;d++){
			head=-1;tail=0;
			for(reg int tmp,k=0;d+w*k<=m;k++){//j=d+w*k
				tmp=f[i-1][d+w*k]-v*k;
				while(tail<=head&&num[head]<=tmp) head--;
				num[++head]=tmp;ind[head]=k;
				while(tail<=head&&k-ind[tail]>tot) tail++;
				f[i][d+w*k]=std::max(f[i][d+w*k],num[tail]+v*k);
			}
		}
	}
	std::printf("%d
",f[n][m]);
}
	return 0;
}
原文地址:https://www.cnblogs.com/suxxsfe/p/13090031.html