单调队列优化DP——习题收集
前言
感觉可以用单调队列优化dp的模型还是挺活的,开个随笔记录一些遇到的比较有代表性的模型,断续更新。主要做一个收集整理总结工作。
记录
0x01
POJ - 1821 Fence,比较适合入门的题,写出转移方程后可以比较容易的看出决策变量的取值范围的界是单调变化的,以及价值拆开之后也是单调的
#include<iostream>
#include<cstdio>
#include<algorithm>
#define dd(x) cout<<#x<<" = "<<x<<" "
#define de(x) cout<<#x<<" = "<<x<<"\n"
#define sz(x) int(x.size())
#define All(x) x.begin(),x.end()
#define pb push_back
#define mp make_pair
#define fi first
#define se second
using namespace std;
typedef long long ll;
typedef long double ld;
typedef pair<int,int> P;
const int maxn=2e4+10,mod=1e9+7,INF=0x3f3f3f3f;
struct node
{
int l,p,s;
}a[maxn];
bool cmp(node x,node y)
{
return x.s<y.s;
}
int q[maxn],h,t;
ll f[105][maxn];
inline ll val(int i,int k)
{
return f[i-1][k]-k*a[i].p;
}
int main()
{
int n,k;
scanf("%d%d",&n,&k);
for (int i=1;i<=k;++i)
scanf("%d%d%d",&a[i].l,&a[i].p,&a[i].s);
sort(a+1,a+k+1,cmp);
for (int i=1;i<=k;++i)
{
h=0,t=-1;
for (int k=max(0,a[i].s-a[i].l);k<a[i].s;++k)
{
while (h<=t&&val(i,k)>=val(i,q[t]))
--t;
q[++t]=k;
}
for (int j=1;j<=n;++j)
{
f[i][j]=max(f[i-1][j],f[i][j-1]);
if (j<a[i].s)
continue;
while (h<=t&&q[h]<j-a[i].l)
++h;
if (h<=t)
f[i][j]=max(f[i][j],val(i,q[h])+j*a[i].p);
}
}
cout<<f[k][n];
return 0;
}
0x02
HDU 3401 trade,被不少博客当做单调队列优化DP的入门题,但我感觉这题要考虑的细节挺多的,并不简单。定义状态f(i,j)为第i天拥有j数量股票的最大收益,转移的思路就是根据不作为、买和卖三种操作来转移。转移时,卖和买操作中对于股票数的枚举用单调队列来优化掉,然后可以注意到我们是不需要枚举第几天来转移的(由于允许某天中不作为,这种操作的存在显然使得有f(i,j)>=f(i-1,j),即天数的增加只可能使收益变大,而不可能更糟),因此直接用i-w-1来转移就好了。因此总复杂度为平方级别,即枚举i,j的复杂度。细节见代码注释。
#include<bits/stdc++.h>
#define dd(x) cout<<#x<<" = "<<x<<" "
#define de(x) cout<<#x<<" = "<<x<<endl
#define sz(x) int(x.size())
#define fi first
#define se second
#define mp make_pair
#define pb push_back
#define All(x) x.begin(),x.end()
using namespace std;
typedef long long ll;
typedef pair<int,int> P;
typedef priority_queue<int> BQ;
typedef priority_queue<int,vector<int>,greater<int> > SQ;
const int maxn=2e3+10,INF=0x3f3f3f3f,mod=1e9+7;
int ap[maxn],bp[maxn],as[maxn],bs[maxn],f[maxn][maxn],q[maxn],h,t;
int main()
{
int T;
cin>>T;
while (T--)
{
int n,maxp,w;
scanf("%d%d%d",&n,&maxp,&w);
for (int i=1;i<=n;++i)
scanf("%d%d%d%d",&ap[i],&bp[i],&as[i],&bs[i]);
for (int i=0;i<=n;++i)
for (int j=0;j<=maxp;++j)
f[i][j]=-INF;
for (int i=1;i<=n;++i)
{
if (i<=w+1) // 注意:当前日期i小于w+1时,若拥有j数量的股票,一定是当天或者前面的某天购买来的,因此如下更新
{
for (int j=0;j<=maxp;++j)
{
if (j<=as[i])
f[i][j]=-ap[i]*j;
f[i][j]=max(f[i][j],f[i-1][j]);
}
}
else
{
// 不作为
for (int j=0;j<=maxp;++j)
f[i][j]=max(f[i][j],f[i-1][j]);
// 买
h=t=0;
q[0]=0;
for (int j=1;j<=maxp;++j)
{
while (h<=t&&j-q[h]>as[i])
++h;
f[i][j]=max(f[i][j],f[i-w-1][q[h]]-(j-q[h])*ap[i]);
while (h<=t&&f[i-w-1][j]+j*ap[i]>=f[i-w-1][q[t]]+q[t]*ap[i])
--t;
q[++t]=j;
}
// 卖
h=t=0;
q[0]=maxp;
for (int j=maxp-1;j>=0;--j)
{
while (h<=t&&q[h]-j>bs[i])
++h;
f[i][j]=max(f[i][j],f[i-w-1][q[h]]+(q[h]-j)*bp[i]);
while (h<=t&&f[i-w-1][j]+j*bp[i]>=f[i-w-1][q[t]]+q[t]*bp[i])
--t;
q[++t]=j;
}
}
}
int ans=-INF;
for (int i=0;i<=maxp;++i)
ans=max(ans,f[n][i]);
printf("%d\n",ans);
}
return 0;
}