suxxsfe

一言(ヒトコト)

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-k\cdot w_i)+k\cdot v_i),k\le num_i\)

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

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

\(d\le w-1,k\le \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\n",f[n][m]);
}
	return 0;
}
posted @ 2020-06-10 23:05  suxxsfe  阅读(205)  评论(0编辑  收藏  举报