【专题系列】单调队列优化DP
Tip:还有很多更有深度的题目,这里不再给出,只给了几道基本的题目(本来想继续更的,但是现在做的题目不是这一块内容,以后有空可能会继续补上)
单调队列——看起来就是很高级的玩意儿,显然是个队列,而且其中的元素还具有单调性
当然,它不只只是一个简单的队列,还是一个双端队列,即队首队尾都可以弹出元素,当然可以用C++自带的STL<deque>实现,当然这篇博客里不建议使用这种写法,因为不开O2的话就会有一个大弊端——慢
单调队列裸题:滑动窗口
线性的求一个区间内的最值,我们先来找个规律,比如这组数据:
8 3
1 3 -1 -3 5 3 6 7(以求最大值为例子)
我们发现前两个数中 1 3 ,3的优先级明显大于1,原因?3比1大,而且3还在1的右边(这样在后面的更新中3还能起到作用,而1显然已经没有作用了,那么我们就可以把1从队列里面弹出去了!)当然,这个操作在3丢到队列里面的时候就可以进行了,同时在这个操作之前,我们还要把前面的元素和现在位置差距大于k的元素弹掉
这样我们便可以保证队列中的元素是单调递增的,那么我们每次在n中取出k个元素的时候,只要把当前队列中的第k个元素放到输出列表中就好了!
当然最小值也是一样的,维护一个单调递减的序列,在这个过程中,每次输出其中最小数
代码如下:(先求最小值,后求最大值)
#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
inline int read(){
int ans=,f=; char chr=getchar();
while(!isdigit(chr)){if(chr=='-') f=-;chr=getchar();}
while(isdigit(chr)) {ans=(ans<<)+chr-;chr=getchar();}
return ans*f;
}
void write(int x){
if(x<) x=-x,putchar('-');
if(x>) write(x/);
putchar(x%+);
}
int q[],h,t,n,a[],k;
int main(){
n=read();k=read();
for(register int i=;i<=n;++i) a[i]=read();
h=,t=;
for(register int i=;i<=n;++i){//Min
while(h<=t&&q[h]+k<=i) ++h;
while(h<=t&&a[i]<=a[q[t]]) --t;
q[++t]=i;
if(i>=k) write(a[q[h]]),putchar(' ');
}puts("");
h=,t=;
for(register int i=;i<=n;++i){//Max
while(h<=t&&q[h]+k<=i) ++h;
while(h<=t&&a[i]>=a[q[t]]) --t;
q[++t]=i;
if(i>=k) write(a[q[h]]),putchar(' ');
}
return ;
}【时间复杂度分析】
显然外循环的复杂度为n,关键在于其中的while循环,分析一下可以知道,每一个元素在其中只会进入队列一次,出队列一次,所以总的时间复杂度为O(n),而且常数也是十分优秀的
当然像这种求区间最值的问题也有一种简单粗暴的方法:线段树
代码如下:(这里代码就折叠掉了,有需求的读者可以自己阅读,就是求区间的最大值和最小值,连修改都不用,可以说是线段树的模板了)
// luogu-judger-enable-o2
#include<iostream>
#include<cstdio>
#include<cctype>
#include<algorithm>
#include<cstring>
#define ll long long
#define lson i << 1,l,m
#define rson i << 1| 1,m + 1,r
#define MAXN (int)1e6 + 5
using namespace std; inline ll read(){
char chr=getchar();
ll f=,ans=;
while(!isdigit(chr)) {if(chr=='-') f=-;chr=getchar();}
while(isdigit(chr)) {ans=ans*;ans+=chr-'';chr=getchar();}
return ans*f; } void write(ll x){
if(x<){
putchar('-');
x=-x;
}
if(x<)
putchar(x+'');
else
write(x/),putchar(x%+);
} struct P{
ll l,r;
ll max,add,min;
ll mid(){
return l + r >> ;
}
}t[MAXN << ];
ll a[MAXN << ]; void build(ll i,ll l,ll r){
t[i].l = l;t[i].r = r;
if(l == r){
t[i].min = a[l];
t[i].max = a[l];
return;
}
ll m = t[i].mid();
build(lson);
build(rson);
t[i].max =max( t[i << ].max , t[i << | ].max );
t[i].min =min( t[i << ].min , t[i << | ].min );
} ll qmin(ll i,ll l,ll r){
if(l <= t[i].l && t[i].r <= r) return t[i].min;
ll pp = 0x3f3f3f3f,qq = 0x3f3f3f3f;
ll m = t[i].mid();
if(l <= m) pp = qmin(i << ,l,r);
if(r > m) qq = qmin(i << | ,l,r);
return min(qq , pp);
} ll qmax(ll i,ll l,ll r){
if(l <= t[i].l && t[i].r <= r) return t[i].max;
ll pp = -0x3f3f3f3f,qq = -0x3f3f3f3f;
ll m = t[i].mid();
if(l <= m) pp = qmax(i << ,l,r);
if(r > m) qq = qmax(i << | ,l,r);
return max(qq , pp);
} ll n,m;
int main(){
n = read() ;
m = read() ;
for(ll i = ;i <= n;i ++)
a[i] = read();
build(,,n);
for(int i = ;i + m - <= n;++i){
printf("%lld",qmin(,i,i+m-));
putchar(' ');
}
puts("");
for(int i = ;i + m - <= n;++i){
printf("%lld",qmax(,i,i+m-));
putchar(' ');
}
return ;
}
【NO.1】
【NOIP提高组初赛程序填空】烽火传递
题目描述
烽火台是重要的军事防御设施,一般建在交通要道或险要处。一旦有军情发生,则白天用浓烟,晚上有火光传递军情。
在某两个城市之间有 nn 座烽火台,每个烽火台发出信号都有一定的代价。为了使情报准确传递,在连续 mm 个烽火台中至少要有一个发出信号。现在输入 n,mn,m 和每个烽火台的代价,请计算总共最少的代价在两城市之间来准确传递情报。
输入格式
第一行是 n,mn,m,表示 nn 个烽火台和连续烽火台数 mm;
第二行 nn 个整数表示每个烽火台的代价 a_iai。
输出格式
输出仅一个整数,表示最小代价。
样例
样例输入
5 3
1 2 5 6 2样例输出
4
样例说明
在第 2,52,5 号烽火台上发信号。
数据范围与提示
n,m正整数且小于等于2×10^5
【分析】
显然是一道DP题
不妨令f[i]表示取第i个点时的最小值
那么有方程:
f[i]=min{f[k]}+a[i]
其中k∈[i-m,i-1]
但显然这样是O(n^2)的复杂度,对于十万级的n,m显然不够优,如果我们可以在很短的时间内求出f[k](k∈[i-m,i-1])的最小值就好了,并且随着i的增大,它每次还能更新入新的数据!
区间最小?单点更新?不就是线段树吗!O(nlogn)的算法出炉(还是一样,先把代码折叠起来,有需求的读者可以自己点开看):(当然,如果读者不会线段树,可以跳过这一部分的代码直接阅读后面)
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define int long long
using namespace std;
inline int read(){
char chr=getchar(); int f=,ans=;
while(!isdigit(chr)) {if(chr=='-') f=-;chr=getchar();}
while(isdigit(chr)) {ans=(ans<<)+(ans<<);ans+=chr-'';chr=getchar();}
return ans*f;
}
void write(int x){
if(x<) putchar('-'),x=-x;
if(x>) write(x/);
putchar(x%+'');
}
int n,m,a[<<];
int minn[<<];
void updata(int i,int l,int r,int pos,int x){
if(l==r){minn[i]=x;return;}
int mid=l+r>>;
if(pos<=mid) updata(i<<,l,mid,pos,x);
else updata(i<<|,mid+,r,pos,x);
minn[i]=min(minn[i<<],minn[i<<|]);
}
int query(int i,int l,int r,int ql,int qr){
if(ql<=l&&r<=qr){return minn[i];}
int mid=l+r>>,x=0x3f3f3f3f,y=0x3f3f3f3f;
if(ql<=mid) x=query(i<<,l,mid,ql,qr) ;
if(qr>mid) y=query(i<<|,mid+,r,ql,qr);
return min(x,y);
}
int f[<<];
signed main(){
freopen("ttt.in","r",stdin);
n=read();m=read();
for(int i=;i<=n;i++)a[i]=read();
for(int i=;i<m;i++) updata(,,n,i,a[i]);
for(int i=m;i<=n;i++){
f[i]=query(,,n,i-m,i-)+a[i];
updata(,,n,i,f[i]);
}int ans=0x7fffffff;
for(int i=n-m+;i<=n;i++) ans=min(f[i],ans);
write(ans);
return ;
}
对于本题,已经可以在要求的时间内求出答案了,但是显然这样的办法有点异常暴力,而且还要照顾一下不会线段树的童鞋是吧,于是切入正题,如何用单调队列做这题!
对于每一次状态的转移,我们只需要维护f[]数组的最值即可,那么显然我们可以以f[]创建一个单调队列,维护f[]的最小值,每次更新它的最新元素,且每次更新即取队首元素即可
当然,上面的while循环可以换成在C++更加里面更加灵活的for循环,本质上还是一个求最值的问题,不过在这之前我们要能从中推出转移方程,关键是要从递推式中看出单调性,这也是我们用单调队列解题的前提
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define int long long
using namespace std;
inline int read(){
char chr=getchar(); int f=,ans=;
while(!isdigit(chr)) {if(chr=='-') f=-;chr=getchar();}
while(isdigit(chr)) {ans=(ans<<)+(ans<<);ans+=chr-'';chr=getchar();}
return ans*f;
}
void write(int x){
if(x<) putchar('-'),x=-x;
if(x>) write(x/);
putchar(x%+'');
}
const int N=1e6+;
int n,m,l,r,a[N],f[N],q[N<<];
signed main(){
n=read();m=read();
for(int i=;i<=n;i++) a[i]=read();
l=r=;
for(int i=;i<=n;i++){
for(;l<r&&i-q[l]>m;l++);
f[i]=f[q[l]]+a[i];
for(;l<r&&f[q[r]]>f[i];r--);
q[++r]=i;
}int ans=0x7fffffff;
for(int i=n-m+;i<=n;i++) ans=min(ans,f[i]);//这里有一个小细节,最后一次选择可以从最后m个元素中选择,原因很简单,只要保证后面m个元素中有一个取就够了
cout<<ans;
return ;
}
【NO.2】
【Tyvj1305】最大子序和
【问题描述】
输入一个长度为n的整数序列,从中找出一段不超过M的连续子序列,使得整个序列的和最大。
例如 1,-3,5,1,-2,3
当m=4时,S=5+1-2+3=7;
当m=2或m=3时,S=5+1=6。【输入格式】
第一行两个数n,m;
第二行有n个数,要求在n个数找到最大子序和。【输出格式】
一个数,数出他们的最大子序和。【输入样例】
6 4
1 -3 5 1 -2 3【输出样例】
7【数据范围】
n,m≤300000;数列元素的绝对值≤1000。【题目来源】
Tyvj1305
【问题分析】
首先,要求连续我们可以把原序列转化成前缀和进行求解
因为前缀和的性质有sum[l~r]=sum[r]-sum[l-1],对于每一个r,我们只要求出前面m个数中最小的sum[l]即可保证sum[l~r]最大
以sum[]建立单调队列即可
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
inline int read(){
char chr=getchar(); int f=,ans=;
while(!isdigit(chr)) {if(chr=='-') f=-;chr=getchar();}
while(isdigit(chr)) {ans=(ans<<)+(ans<<);ans+=chr-'';chr=getchar();}
return ans*f;
}
void write(int x){
if(x<) putchar('-'),x=-x;
if(x>) write(x/);
putchar(x%+'');
}
const int M=;
int n,m;
int q[M],a[M],h,t,f[M];
int sum[M];
int main(){
n=read(),m=read();
for(int i=;i<=n;i++) a[i]=read(),sum[i]=sum[i-]+a[i];
int l=,r=;int ans=-0x7fffffff;
for(int i=;i<=n;i++){
for(;l<r&&i-q[l]>m;l++);
ans=max(ans,sum[i]-sum[q[l]]);
for(;l<r&&sum[q[r]]>=sum[i];r--);
q[++r]=i;
}
cout<<ans;
return ;
}
【NO.3】
【Hdu3530】Subsequence
【问题描述】
给定一个包含n个整数序列,求满足条件的最长区间的长度:该区间内的最大数和最小数的差不小于m,且不大于k。【输入格式】
输入包含多组测试数据:对于每组测试数据:
第一行,包含三个整数n,m和k;
第二行,包含n个整数的序列。【输出格式】
对于每组测试数据,输出满足条件的最长区间的长度。【输入样例】
5 0 0
1 1 1 1 1
5 0 3
1 2 3 4 5【输出样例】
5
4【数据范围】
1≤n≤100000;
0≤m,k≤100000;
0≤ai≤100000【题目来源】
Hdu3530
【题目分析】
其实也是模板题了,只不过要求同时维护最大值最小值而已
这里不再提最大值最小值的更新了,而是给出这道题里新的东西:要使最大值减最小值在区间[l,r]中的话,一旦当前的序列最大值减最小值不再该区间内了,便要继续弹出元素
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
inline int read(){
char chr=getchar(); int f=,ans=;
while(!isdigit(chr)) {if(chr=='-') f=-;chr=getchar();}
while(isdigit(chr)) {ans=(ans<<)+(ans<<);ans+=chr-'';chr=getchar();}
return ans*f;
}
void write(int x){
if(x<) putchar('-'),x=-x;
if(x>) write(x/);
putchar(x%+'');
}
const int M=;
int q1[M],q2[M],a[M],n,m,k,t1,t2,tt1,tt2,ttt1,ttt2,ans;
int main(){
while(~scanf("%d%d%d",&n,&m,&k))
{
for(int i=;i<=n;i++) a[i]=read();
memset(q1,,sizeof(q1));
memset(q2,,sizeof(q2));
t1=;t2=;ttt1=;ttt2=;ans=;tt1=;tt2=;
for(int i=;i<=n;i++){
while(t1<ttt1&&a[q1[ttt1-]]<=a[i])ttt1--; //maxn
q1[ttt1++]=i;
while(t2<ttt2&&a[q2[ttt2-]]>=a[i])ttt2--; //minn
q2[ttt2++]=i;
while(a[q1[t1]]-a[q2[t2]]>k)
if(q1[t1]<q2[t2]) tt1=q1[t1++];
else tt2=q2[t2++];
if(a[q1[t1]]-a[q2[t2]]>=m)
ans=max(ans,i-max(tt1,tt2));
}
write(ans),puts("");
}
return ;
}
【专题系列】单调队列优化DP的更多相关文章
- 动态规划专题(四)——单调队列优化DP
前言 单调队列优化\(DP\)应该还算是比较简单容易理解的吧,像它的升级版斜率优化\(DP\)就显得复杂了许多. 基本式子 单调队列优化\(DP\)的一般式子其实也非常简单: \[f_i=max_{j ...
- 单调队列优化DP,多重背包
单调队列优化DP:http://www.cnblogs.com/ka200812/archive/2012/07/11/2585950.html 单调队列优化多重背包:http://blog.csdn ...
- bzoj1855: [Scoi2010]股票交易--单调队列优化DP
单调队列优化DP的模板题 不难列出DP方程: 对于买入的情况 由于dp[i][j]=max{dp[i-w-1][k]+k*Ap[i]-j*Ap[i]} AP[i]*j是固定的,在队列中维护dp[i-w ...
- hdu3401:单调队列优化dp
第一个单调队列优化dp 写了半天,最后初始化搞错了还一直wa.. 题目大意: 炒股,总共 t 天,每天可以买入na[i]股,卖出nb[i]股,价钱分别为pa[i]和pb[i],最大同时拥有p股 且一次 ...
- Parade(单调队列优化dp)
题目连接:http://acm.hdu.edu.cn/showproblem.php?pid=2490 Parade Time Limit: 4000/2000 MS (Java/Others) ...
- BZOJ_3831_[Poi2014]Little Bird_单调队列优化DP
BZOJ_3831_[Poi2014]Little Bird_单调队列优化DP Description 有一排n棵树,第i棵树的高度是Di. MHY要从第一棵树到第n棵树去找他的妹子玩. 如果MHY在 ...
- 【单调队列优化dp】 分组
[单调队列优化dp] 分组 >>>>题目 [题目] 给定一行n个非负整数,现在你可以选择其中若干个数,但不能有连续k个数被选择.你的任务是使得选出的数字的和最大 [输入格式] ...
- [小明打联盟][斜率/单调队列 优化dp][背包]
链接:https://ac.nowcoder.com/acm/problem/14553来源:牛客网 题目描述 小明很喜欢打游戏,现在已知一个新英雄即将推出,他同样拥有四个技能,其中三个小技能的释放时 ...
- 单调队列以及单调队列优化DP
单调队列定义: 其实单调队列就是一种队列内的元素有单调性的队列,因为其单调性所以经常会被用来维护区间最值或者降低DP的维数已达到降维来减少空间及时间的目的. 单调队列的一般应用: 1.维护区间最值 2 ...
- BZOJ1791[Ioi2008]Island 岛屿 ——基环森林直径和+单调队列优化DP+树形DP
题目描述 你将要游览一个有N个岛屿的公园.从每一个岛i出发,只建造一座桥.桥的长度以Li表示.公园内总共有N座桥.尽管每座桥由一个岛连到另一个岛,但每座桥均可以双向行走.同时,每一对这样的岛屿,都有一 ...
随机推荐
- redis在linux下安装以及扩展
安装过redis后发现回头忘了,今天重新安装记录下 首先 我是在home下创建redis文件 mkdir redis 然后直接用wget安装 wget http://download.redis ...
- git学习(1)
一.git fetch 和git pull 的差别 1.git fetch 相当于是从远程获取最新到本地,不会自动merge,如下指令: git fetch orgin master //将远程仓库的 ...
- LINQ简记(3):子句
LINQ查询表达式的子句如select,where,from等都是比较简单的子句,相信各位多练习练习,再结合MSDN的例子,基本上是可以理解的,因此,本文只挑几个有代表性的,以及有些难理解的子句来简述 ...
- Problem 28
Problem 28 https://projecteuler.net/problem=28 Starting with the number 1 and moving to the right in ...
- rpm包下载地址
https://dl.fedoraproject.org/pub/epel/6/x86_64/
- mybatis源码阅读-初始化过程(七)
说明 mybatis初始化过程 就是解析xml到封装成Configuration对象 供后续使用 SqlSessionFactoryBuilder 代码例子 SqlSessionFactoryBuil ...
- spring boot 中访问 REST 接口
RestTemplate restTemplate = new RestTemplate(); Object result = restTemplate.getForObject("http ...
- 无管理员帐号的WIN7,如果使用自己的JDK版本?
因为公司的电脑只有普通权限, 而且JDK版本低了. 那我只好用BAT脚本来导入自己的环境啦,毕竟每次在CMD窗口输入太繁琐. set JAVA_HOME=D:\JDK8 set CLASSPATH=D ...
- [bzoj1047][HAOI2007]理想的正方形_动态规划_单调队列
理想的正方形 bzoj-1047 HAOI-2007 题目大意:有一个a*b的整数组成的矩阵,现请你从中找出一个n*n的正方形区域,使得该区域所有数中的最大值和最小值的差最小. 注释:$2\le a, ...
- django 和 mysql的一次troubleshooting
下面是一次用django连接mysql的经历,记录下来也许以后会有帮助. 首先是用django的./manage.py syncdb 去连接mysql -bash-3.2$ ./manage.py s ...