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}\)
所以没做一次单调队列的循环次数就是
也就是 \(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;
}