【专题系列】单调队列优化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座桥.尽管每座桥由一个岛连到另一个岛,但每座桥均可以双向行走.同时,每一对这样的岛屿,都有一 ...
随机推荐
- Java中的接口详解
接口 是Java语言中一种引用类型,是方法的集合,如果说类的内部封装了成员变量.构造方法和成员方法,那么接口的内部主要就是封装了方法,包含抽象方法(JDK 7及以前),默认方法和静态方法(JDK 8) ...
- Altium Designer 2017 ActiveRoute使用以及其他技巧
ActiveRoute 点击右下角PCB->PCB ActiveRoute调出ActiveRoute面板 在设计电路时,有一堆细小的白色线,表示几个脚之间需要连接,按住键盘Alt + 鼠标左键, ...
- Linux内核系统调用处理过程
原创作品转载请注明出处 + https://github.com/mengning/linuxkernel/ 学号末三位:168 下载并编译Linux5.0 xz -d linux-.tar.xz . ...
- tomcat 编码设置
在Tomcat8.0之前的版本,如果你要向服务器提交中文是需要转码的(如果你没有修改server.xml中的默认编码),因为8.0之前Tomcat的默认编码为ISO8859-1. POST方式提交 r ...
- kesci---2019大数据挑战赛预选赛---情感分析
一.预选赛题------文本情感分类模型 本预选赛要求选手建立文本情感分类模型,选手用训练好的模型对测试集中的文本情感进行预测,判断其情感为「Negative」或者「Positive」.所提交的结果按 ...
- 68.document增删改原理
主要知识点 document增的原理 document删的原理 document改的原理 一.document增的原理 一个document存入es大致要分以下几个步骤 (1)数据写入buffer, ...
- Problem 56
Problem 56 https://projecteuler.net/problem=56 Powerful digit sum A googol (10100) is a massive numb ...
- ansible - 基本用法
目录 ansible - 01 一. 安装与使用 ansible命令格式 查看ansible生成的配置文件 ssh认证方式 ansible的第一个命令 弱口令校验 host-pattern的格式 模块 ...
- springcloud(五):Eureka提供数据的客户端连接Docker的mysql
一.提供数据的客户端需要连接数据了,因此需要我们使用mybatis了,等下使用idea生成mybaits和web的依赖 二.提供数据的客户端项目 1.创建项目 2.选择idea自动给我们生成的依赖 3 ...
- JSTL 实现 为Select赋多个值
需要注意需要在.jsp文件中引入相应的类库 <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core ...