「学习笔记」单调队列优化dp
算法
使用单调队列优化dp 废话
对与一些dp的转移方程,我们可以通过拆使它与某个区间的最值相关。
这时可以用单调队列算出区间最值,进行优化。
例题
最大子段和
题意
给出一个长度为 \(n\) 的整数序列,从中找出一段长度不超过 \(m\) 的连续子序列,使得整个序列的和最大。
思路
设 \(sum_i\) 为 \(i\) 的前缀和,易得答案为:
\]
其中 \(\min_\limits{i-m\le k\le i-1}\{sum_k\}\) 这部分可以用单调队列快速求出。
那么算起来就变得简单多了。
代码
点击查看代码
#include<bits/stdc++.h>
#define _for(i,a,b) for(int i=a;i<=b;++i)
#define for_(i,a,b) for(int i=a;i>=b;--i)
#define ll long long
using namespace std;
const int N=3e5+10;
int n,m,a[N],sum[N],f[N],ans;
int q[N],h=1,t=0;
inline int rnt(){
int x=0,w=1;char c=getchar();
while(c<'0'||c>'9'){if(c=='-')w=-1;c=getchar();}
while(c>='0'&&c<='9')x=(x<<3)+(x<<1)+(c^48),c=getchar();
return x*w;
}
void tmp(int k){
while(h<=t&&q[h]<k-m)++h;
ans=max(ans,sum[k]-sum[q[h]]);
while(h<=t&&sum[q[t]]>sum[k])--t;
q[++t]=k;
}
int main(){
n=rnt(),m=rnt();
_for(i,1,n){
a[i]=rnt();
sum[i]=sum[i-1]+a[i];
tmp(i);
}
printf("%d\n",ans);
return 0;
}
修剪草坪
题意
给定一个 \(n\ (1\le n\le 10^5)\) 个元素的序列,可任选多个数,要求任意一段连续选的数长度不能超过 \(k\)。
\(1\le N\le 10^5\)
思路
设 \(f_{i,0/1}\) 表示第 \(i\) 个数选(1)或是不选(0),\(sum_i\) 表示 \(i\) 的前缀和。
转移方程为:
f_{i,1}=\max_\limits{i-k\le j<i}\{f_{j,0}+(sum_i-sum_j)\}
\]
\(f_{i,1}\) 转移方程可以拆成:
\]
用单调队列维护 \(f_{j,0}-sum_j\) 即可。
代码
点击查看代码
#include<bits/stdc++.h>
#define _for(i,a,b) for(ll i=a;i<=b;++i)
#define for_(i,a,b) for(ll i=a;i>=b;--i)
#define ll long long
using namespace std;
const ll N=3e5+10;
ll n,k,a[N],sum[N],f[N][2],ans;
ll q[N],h=1,t=0;
inline ll rnt(){
ll x=0,w=1;char c=getchar();
while(c<'0'||c>'9'){if(c=='-')w=-1;c=getchar();}
while(c>='0'&&c<='9')x=(x<<3)+(x<<1)+(c^48),c=getchar();
return x*w;
}
void tmp(ll i){
while(h<=t&&q[h]<i-k)++h;
f[i][1]=f[q[h]][0]+sum[i]-sum[q[h]];
while(h<=t&&f[q[t]][0]-sum[q[t]]<f[i][0]-sum[i])--t;
q[++t]=i;
}
int main(){
n=rnt(),k=rnt();
tmp(0);
_for(i,1,n){
a[i]=rnt();
sum[i]=sum[i-1]+a[i];
f[i][0]=max(f[i-1][0],f[i-1][1]);
tmp(i);
}
printf("%lld\n",max(f[n][0],f[n][1]));
return 0;
}
瑰丽华尔兹
题意
给出一个 \(N*M\) 的船上地图,有空地也有家具。
船上有 \(K\) 段时间,在每段时间都会往不同的方向倾斜,钢琴也会朝着那个方向倾斜,但不允许碰上家具。
求钢琴最长的滑行距离。
\(1\le N,M\le200,K\le200\)
思路
设 \(f_{i,j,k}\) 表示在第 \(i\) 段时间滑行到 \(j,k\) 的位置。
那么转移方程就是:
\]
瞎写的
按着倾斜的方向遍历,再用单调队列优化优化就好了。
代码
点击查看代码
#include<bits/stdc++.h>
#define _for(i,a,b) for(int i=a;i<=b;++i)
#define for_(i,a,b) for(int i=a;i>=b;--i)
#define ll long long
using namespace std;
const int N=210,inf=0x3f3f3f3f;
int n,m,x,y,k,mp[N][N];
int f[N][N][N],la[N][N][N],ans;
inline ll rnt(){
ll x=0,w=1;char c=getchar();
while(c<'0'||c>'9'){if(c=='-')w=-1;c=getchar();}
while(c>='0'&&c<='9')x=(x<<3)+(x<<1)+(c^48),c=getchar();
return x*w;
}
inline char rch(){
char c=getchar();
while(c!='.'&&c!='x')c=getchar();
return c;
}
struct dq{//deque
int q[N],h,t;
void nw(){
memset(q,0,sizeof(q));
h=1,t=0;
}
bool empty(){return h>t;}
int front(){return q[h];}
int back(){return q[t];}
void pop_f(){++h;}
void pop_b(){--t;}
void push(int x){q[++t]=x;}
};
void dp(int d,int s,int t,int fx){
int len=(t-s+1);
if(fx==1){
_for(j,1,m){
dq q;q.nw();
for_(i,n,1){
if(mp[i][j]){
while(!q.empty())q.pop_f();
continue;
}
while(!q.empty()&&q.front()>i+len)q.pop_f();
while(!q.empty()&&f[d-1][q.back()][j]+q.back()-i<f[d-1][i][j])q.pop_b();
q.push(i);
if(f[d-1][q.front()][j]>-1)
f[d][i][j]=f[d-1][q.front()][j]+q.front()-i;
ans=max(ans,f[d][i][j]);
}
}
}
else if(fx==2){
_for(j,1,m){
dq q;q.nw();
_for(i,1,n){
if(mp[i][j]){
while(!q.empty())q.pop_f();
continue;
}
while(!q.empty()&&q.front()<i-len)q.pop_f();
while(!q.empty()&&f[d-1][q.back()][j]+i-q.back()<f[d-1][i][j])q.pop_b();
q.push(i);
if(f[d-1][q.front()][j]>-1)
f[d][i][j]=f[d-1][q.front()][j]+i-q.front();
ans=max(ans,f[d][i][j]);
}
}
}
else if(fx==3){
_for(i,1,n){
dq q;q.nw();
for_(j,m,1){
if(mp[i][j]){
while(!q.empty())q.pop_f();
continue;
}
while(!q.empty()&&q.front()>j+len)q.pop_f();
while(!q.empty()&&f[d-1][i][q.back()]+q.back()-j<f[d-1][i][j])q.pop_b();
q.push(j);
if(f[d-1][i][q.front()]>-1)
f[d][i][j]=f[d-1][i][q.front()]+q.front()-j;
ans=max(ans,f[d][i][j]);
}
}
}
else{
_for(i,1,n){
dq q;q.nw();
_for(j,1,m){
if(mp[i][j]){
while(!q.empty())q.pop_f();
continue;
}
while(!q.empty()&&q.front()<j-len)q.pop_f();
while(!q.empty()&&f[d-1][i][q.back()]+j-q.back()<f[d-1][i][j])q.pop_b();
q.push(j);
if(f[d-1][i][q.front()]>-1)
f[d][i][j]=f[d-1][i][q.front()]+j-q.front();
ans=max(ans,f[d][i][j]);
}
}
}
}
int main(){
n=rnt(),m=rnt(),x=rnt(),y=rnt(),k=rnt();
_for(i,1,n)
_for(j,1,m)
mp[i][j]=(bool)(rch()=='x');
memset(f,-inf,sizeof(f));
f[0][x][y]=0;
_for(i,1,k){
int s=rnt(),t=rnt(),fx=rnt();
dp(i,s,t,fx);
}
printf("%d\n",ans);
return 0;
}
股票交易
题意
一共有 \(T\) 天,知道了每天股票的买入金额 \(ap_i\),卖出金额 \(bp_i\),买入限制 \(as_j\),卖出限制 \(bs_j\)。要求持有股票数不能超过 \(MaxP\) ,两次交易相隔 \(w\) 天。
\(1\le w<T\le 2*10^3,1\le MaxP\le 2*10^3\)
思路
设 \(f_{i,j}\) 表示第 \(i\) 天持有 \(j\) 个股票的最大钱数。
有四种情况:
- 凭空买
转移方程:
\]
- 不买不卖
转移方程:
\]
- 只买
转移方程:
\]
- 只卖
转移方程:
\]
此时时间复杂度是 \(O(T*MaxP^2)\)。
观察只买和只卖的情况,可以发现它们的转移方程可以用单调队列优化掉一维。
那么时间复杂度就被优化成了 \(O(T*MaxP)\),完全可过。
代码
点击查看代码
#include<bits/stdc++.h>
#define _for(i,a,b) for(ll i=a;i<=b;++i)
#define for_(i,a,b) for(ll i=a;i>=b;--i)
#define ll long long
using namespace std;
const ll N=4010,inf=0x3f3f3f3f;
ll t,mxp,w,ap[N],bp[N],as[N],bs[N];
inline ll rnt(){
ll x=0,w=1;char c=getchar();
while(c<'0'||c>'9'){if(c=='-')w=-1;c=getchar();}
while(c>='0'&&c<='9')x=(x<<3)+(x<<1)+(c^48),c=getchar();
return x*w;
}
struct dq{//deque
ll q[N],h,t;
void nw(){
memset(q,0,sizeof(q));
h=1,t=0;
}
bool empty(){return h>t;}
ll front(){return q[h];}
ll back(){return q[t];}
void pop_f(){++h;}
void pop_b(){--t;}
void push(int x){q[++t]=x;}
};
namespace SOLVE{
ll f[N][N];
void DpPkm(ll i){//凭空买
_for(j,0,min(mxp,as[i]))
f[i][j]=-ap[i]*j;
return;
}
void DpBmbm(ll i){//不买也不卖
_for(j,0,mxp)
f[i][j]=max(f[i][j],f[i-1][j]);
return;
}
void DpBuy(ll i){//原基础上买来
dq q;q.nw();
_for(j,0,mxp){
while(!q.empty()&&j-q.front()>as[i])q.pop_f();
while(!q.empty()&&f[i-w-1][j]>f[i-w-1][q.back()]-(j-q.back())*ap[i])q.pop_b();
q.push(j);
f[i][j]=max(f[i][j],f[i-w-1][q.front()]-(j-q.front())*ap[i]);
}
return;
}
void DpSell(ll i){//原基础上卖出
dq q;q.nw();
for_(j,mxp,0){
while(!q.empty()&&q.front()-j>bs[i])q.pop_f();
while(!q.empty()&&f[i-w-1][j]>f[i-w-1][q.back()]+(q.back()-j)*bp[i])q.pop_b();
q.push(j);
f[i][j]=max(f[i][j],f[i-w-1][q.front()]+(q.front()-j)*bp[i]);
}
return;
}
ll Solve(){
memset(f,-inf,sizeof(f));
_for(i,1,t){
DpPkm(i);
DpBmbm(i);
if(i-w>1){
DpBuy(i);
DpSell(i);
}
}
return f[t][0];
}
}
int main(){
t=rnt(),mxp=rnt(),w=rnt();
_for(i,1,t)
ap[i]=rnt(),bp[i]=rnt(),as[i]=rnt(),bs[i]=rnt();
printf("%lld\n",SOLVE::Solve());
return 0;
}/*
*/
$$
\Huge{\mathfrak{The\ End}}
$$
「学习笔记」单调队列优化dp的更多相关文章
- 「学习笔记」FFT 之优化——NTT
目录 「学习笔记」FFT 之优化--NTT 前言 引入 快速数论变换--NTT 一些引申问题及解决方法 三模数 NTT 拆系数 FFT (MTT) 「学习笔记」FFT 之优化--NTT 前言 \(NT ...
- bzoj1499: [NOI2005]瑰丽华尔兹&&codevs1748 单调队列优化dp
这道题 网上题解还是很多很好的 强烈推荐黄学长 码风真的好看 神犇传送门 学习学习 算是道单调队列优化dp的裸题吧 #include<cstdio> #include<cstring ...
- 「单调队列优化DP」P2034 选择数字
「单调队列优化DP」P2034 选择数字 题面描述: 给定一行n个非负整数a[1]..a[n].现在你可以选择其中若干个数,但不能有超过k个连续的数字被选择.你的任务是使得选出的数字的和最大. 输入格 ...
- 【笔记篇】单调队列优化dp学习笔记&&luogu2569_bzoj1855股票交♂易
DP颂 DP之神 圣洁美丽 算法光芒照大地 我们怀着 崇高敬意 跪倒在DP神殿里 你的复杂 能让蒟蒻 试图入门却放弃 在你光辉 照耀下面 AC真心不容易 dp大概是最经久不衰 亘古不化的算法了吧. 而 ...
- 算法笔记--单调队列优化dp
单调队列:队列中元素单调递增或递减,可以用双端队列实现(deque),队列的前面和后面都可以入队出队. 单调队列优化dp: 问题引入: dp[i] = min( a[j] ) ,i-m < j ...
- 【学习笔记】动态规划—斜率优化DP(超详细)
[学习笔记]动态规划-斜率优化DP(超详细) [前言] 第一次写这么长的文章. 写完后感觉对斜优的理解又加深了一些. 斜优通常与决策单调性同时出现.可以说决策单调性是斜率优化的前提. 斜率优化 \(D ...
- Parade(单调队列优化dp)
题目连接:http://acm.hdu.edu.cn/showproblem.php?pid=2490 Parade Time Limit: 4000/2000 MS (Java/Others) ...
- 1855: [Scoi2010]股票交易[单调队列优化DP]
1855: [Scoi2010]股票交易 Time Limit: 5 Sec Memory Limit: 64 MBSubmit: 1083 Solved: 519[Submit][Status] ...
- 「学习笔记」FFT 快速傅里叶变换
目录 「学习笔记」FFT 快速傅里叶变换 啥是 FFT 呀?它可以干什么? 必备芝士 点值表示 复数 傅立叶正变换 傅里叶逆变换 FFT 的代码实现 还会有的 NTT 和三模数 NTT... 「学习笔 ...
随机推荐
- ubuntu使用postfix和AWS-SES发送邮件
在日常开发中,邮件发送是个比较常见的场景.因此出现了很多相关的软件和服务,各大云厂商也推出自己的邮件服务.今天笔者就像大家介绍一种常见的组合,AWS的邮件服务 SES 与邮件服务器 postfix 的 ...
- MongoDB学习总览
第1部分: MongoDB入门(第1~6章) 该部分介绍MongoDB的基本概念及入门知识. 通过该部分的学习,读者可对MongoDB自身的技术全貌形成一定的认识. 第2部分: MongoDB微服务开 ...
- Linux如何安装JDK1.8版本详细步骤
Linux如何安装JDK1.8版本详细步骤 1.下载JDK1.8版本压缩包 进入官网:https://www.oracle.com/java/technologies/downloads/ 2.将压缩 ...
- service继承baseService后无法注入dao的解决办法
1.在set方法上加@Autowired 2.在set方法上加@Resource 这样子就可以拿到dao了
- distroless 镜像介绍及 基于cbl-mariner的.NET distroless 镜像的容器
1.概述 容器改变了我们看待技术基础设施的方式.这是我们运行应用程序方式的一次巨大飞跃.容器编排和云服务一起为我们提供了一种近乎无限规模的无缝扩展能力. 根据定义,容器应该包含 应用程序 及其 运行时 ...
- 过年了,基于Vue做一个消息通知组件
前言 今天除夕,在这里祝大家新年快乐!!!今天在这个特别的日子里我们做一个消息通知组件,好,我们开始行动起来吧!!!项目一览 效果很简单,就是这种的小卡片似的效果. 我们先开始写UI页面,可自定义消息 ...
- Failed to Setup IP tables: Unable to enable SKIP DNAT rule: (iptables failed: iptables --wait -t nat -I DOCKER -i br-b1938128a963
报错信息:Failed to Setup IP tables: Unable to enable SKIP DNAT rule: (iptables failed: iptables --wait ...
- 实现一个Prometheus exporter
Prometheus 官方和社区提供了非常多的exporter,涵盖数据库.中间件.OS.存储.硬件设备等,具体可查看exporters.exporterhub.io,通过这些 exporter 基本 ...
- NC13328 倒水
NC13328 倒水 题目 题目描述 有一个大水缸,里面水的温度为 \(T\) 单位,体积为 \(C\) 升.另有 \(n\) 杯水(假设每个杯子的容量是无限的),每杯水的温度为 \(t[i]\) 单 ...
- 图片64base转码与解码
场景一:图片转码成base64,传输,接收后解码成png等格式图片 import base64 # 读取图片,转换为base64编码格式 with open("F:\Archer\pictu ...