单调队列

luogu-单调队列/单调栈专题

大兔子大兔子 提交于 2019-12-05 09:53:24
P2698 [USACO12MAR]花盆Flowerpot 题意: 给出水滴的坐标与下落时间,你需要构造一个盆,使他的宽度满足 在其范围内能够接住水滴时间(第一滴和最后一滴/最大与最小值)时间差大于等于k,且使得这个盆的直径最小 思路: 会想到尺取法(双指针)来在符合条件内缩小范围,同时又涉及到区间最值问题,那么上来想就很容易想到单调队列或者单调栈来维护区间最值 两点:满足区间最小与最大之差大于k,且使得其长度最小。 (1)区间最值:由于是要进行双指针操作:所以选择双端队列来实现单调队列维护最值,且能维护在 q.front().x ~ q.back().x的区间最值 (2)在符合条件下对 front/队首进行移动,以达到缩小区间操作 code: #include <bits/stdc++.h> using namespace std; const int maxn = 1e5+7; const int inf = 0x3f3f3f3f; struct node{ int x,y; }; bool cmp(node a,node b){ return a.x<b.x; } node arr[maxn]; deque<node>q1; deque<node>q2; int main(){ int n,k; cin>>n>>k; for(int i=0;i<n;i++){ cin>

单调队列专题题解

流过昼夜 提交于 2019-12-04 09:09:56
\(T1:[POI2010]PIL-Pilots\) 题意:给定 \(n,k\) 和一个长度为 \(n\) 的序列,求最长的最大值最小值相差不超过 \(k\) 的序列的长度. 分析:维护两个单调队列,一个维护最大值,一个维护最小值,每次更新前先检查两个队列的队头的下标是否相差在 \(k\) 以内,如果大于则令下标较小的队头出队,同时记录当前出队的下标最小值,用于后面更新答案. #include<iostream> #include<cstdio> #include<algorithm> #include<cstring> #include<cmath> #include<queue> #include<map> #include<set> #define ll long long using namespace std; inline int read(){ int x=0,o=1;char ch=getchar(); while(ch!='-'&&(ch<'0'||ch>'9'))ch=getchar(); if(ch=='-')o=-1,ch=getchar(); while(ch>='0'&&ch<='9')x=x*10+ch-'0',ch=getchar(); return x*o; } const int N=1e6+5; int k,n,a[N],q1[N],q2[N]

luogu P1714 切蛋糕 |单调队列

ε祈祈猫儿з 提交于 2019-12-04 08:11:58
题目描述 今天是小Z的生日,同学们为他带来了一块蛋糕。这块蛋糕是一个长方体,被用不同色彩分成了N个相同的小块,每小块都有对应的幸运值。 小Z作为寿星,自然希望吃到的第一块蛋糕的幸运值总和最大,但小Z最多又只能吃M小块(M≤N)的蛋糕。 吃东西自然就不想思考了,于是小Z把这个任务扔给了学OI的你,请你帮他从这N小块中找出连续的k块蛋糕(k≤M),使得其上的幸运值最大。 输入格式 输入文件cake.in的第一行是两个整数N,M。分别代表共有N小块蛋糕,小Z最多只能吃M小块。 第二行用空格隔开的N个整数,第i个整数Pi代表第i小块蛋糕的幸运值。 输出格式 输出文件cake.out只有一行,一个整数,为小Z能够得到的最大幸运值。 #include<deque> #include<cstdio> #include<cstring> #include<iostream> #include<algorithm> using namespace std; const int N=5e5+10; int p[N],l,r; deque<int>q; signed main(){ int n,m; cin>>n>>m; for(int i=1;i<=n;i++)scanf("%d",&p[i]),p[i]+=p[i-1]; int ans=-(1<<30); for(int i=1;i<=n;i++)

单调队列的实现

匿名 (未验证) 提交于 2019-12-03 00:40:02
单调队列的作用,归纳成一句话就是不断读入元素,不时去掉元素,随时查询最值 应用的话就比较高级了,除了滑动窗口这个题目,还有动态规划的效率优化,当然还有别的以后再整理 单调队列由于要不断从两端进行队列操作,所以采用双端队列来实现 在此例中,所有的数据都是放在a数组中的,我们用一个p来表示a数组中某一个元素的下标,然后在单调队列中存下标即可 我们以维护单调递增队列举例 while (!q.empty()&&a[p]<=a[q.back()]) // 维护单调队列 q.pop_back(); q.push_back(p); 对于一个新的p,我们用a[p]和当前单调队列的最后一个位置的元素作比较,如果发现比新插入元素的大的,全部扔出去,因为我这个队列只维护单调增,只方便找最小值即可,大的没用 然而,对于一些过期的元素,虽然他们很小,我们还是要把它们扔掉才可以 while (!q.empty()&&q.front()<p-k+ 1 ) // 维持题意的区间长度 q.pop_front(); // 移出出界的元素 接下来根据题意输出队列中元素的值即可 由于我们是单调递增队列,维护的是最小值,那么队首元素就是最有用的 if (p>=k- 1 ) // 元素进够k个后开始输出答案 cout<<a[q.front()]<< " " ; p ++; 下面给出滑动窗口的完整实现 1 //

浅谈单调队列优化DP

匿名 (未验证) 提交于 2019-12-03 00:14:01
@ TOC 这篇是搬运自己在洛谷写的题解 题目链接: \(luoguP1725\) 相信读完题就能看出这是一道简单的DP题 状态转移 状态转移方程很容易想到。 即: dp[ i ] = max{区间内的最大dp值}+该点的权值 然而这种算法的最坏复杂度为$O(n^2)$,而本题的数据是$1e5$级别的,这样的复杂度显然不正确,必须优化。 优化 哪一步可以优化呢? 可以发现这样转移有着冗杂的枚举区间内元素的过程。 我们又发现每次跳的区间都是一个固定长度,自然而然地想到了一个经典的问题: 滑动窗口问题 ,即单调队列,可以让我们做到$O(n)$预处理,$O(1)$找到任意固定长度区间内的最值。 有了单调队列,我们的dp过程可以省去枚举区间内的每个元素的过程,总复杂度也由$O(n^2)$降到$O(n)$,完全没有压力。 如何实现 在代码中由注释进行讲解: #include<cstdio> using namespace std; #define oo 0x3f3f3f3f #define ri register int const int N = 200005; int n,l,r,head = 1,tail = 0,cur = 0,ans = -oo,a[N],q[N],dp[N]; //head,tail分别是单调队列的首尾指针,cur是指向当前待入队列的元素,a存权值,q为单调队列

poj2373(单调队列优化dp)

匿名 (未验证) 提交于 2019-12-03 00:13:02
传送门 题意是每个洒水装置的半径范围为[A,B],每头奶牛有自己的一个区间[s,e],这个区间只能由一个洒水装置覆盖。 要求整个区间[1,L](L<=1e6)不重叠的被覆盖,最少要多少个洒水装置,洒水装置的范围不可以超过整体区间的范围。 我们用not_r[]来打个标记。 看到L的范围1e6,我们知道应该是使用O(n)的算法。 设f[i]表示处理到i这个位置需要的洒水装置个数。(i一定是 右端点 )。 f[i]=min(f[j])+1(A<=(i-j)/2<=B) 直接循环时间O(n^2)考虑优化。 我们发现对于点i,可以用的j一定是一段连续的区间,所以单队维护最小的f[j]。 因为一个j能不能被使用还要看它的位置是否满足限制,所以用队列id存一下下标。 因为i是右端点,所以在循环的时候,我们是偶数个的增加。 #include<cstdio> #include<iostream> #include<cstring> #define LL long long #define INF 2100000000 #define N 1000003 #define re register using namespace std; int read() { int x=0,f=1;char s=getchar(); while(s<'0'||s>'9'){if(s=='-')f=-1;s

cogs 495. 滑动窗口 单调队列

匿名 (未验证) 提交于 2019-12-02 23:55:01
495. 滑动窗口 ★★ 输入文件: window.in 输出文件: window.out 简单对比 时间限制:2 s 内存限制:256 MB 【问题描述】 给你一个长度为N的数组,一个长为K的滑动的窗体从最左移至最右端,你只能见到窗口的K个数,每次窗体向右移动一位,如下表: Window position Min value Max value [ 1 3 -1 ] -3 5 3 6 7 -1 3 1 [ 3 -1 -3 ] 5 3 6 7 -3 3 1 3 [ -1 -3 5 ] 3 6 7 -3 5 1 3 -1 [ -3 5 3 ] 6 7 -3 5 1 3 -1 -3 [ 5 3 6 ] 7 3 6 1 3 -1 -3 5 [ 3 6 7 ] 3 7 你的任务是找出窗口在各位置时的max value,min value. 【输入格式】 第一行n,k,第二行为长度为n的数组 【输出格式】 第一行每个位置的min value,第二行每个位置的max value 【输入输出样例】 window.in 8 3 1 3 -1 -3 5 3 6 7 window.out -1 -3 -3 -3 3 3 3 3 5 5 6 7 【数据范围】 20%:n≤500; 50%:n≤100000;100%:n≤1000000; deque <int> q ; #include

单调栈and单调队列

匿名 (未验证) 提交于 2019-12-02 23:52:01
单调栈 单调栈是一种特殊的栈,特殊之处在于栈内的元素都保持一个单调性,可能为单调递增,也可能为单调递减。 性质: 单调栈里的元素具有单调性 元素加入栈前,会在栈顶端把破坏栈单调性的元素都删除 使用单调栈可以找到元素向左遍历第一个比他小的元素,也可以找到元素向左遍历第一个比他大的元素。 单调栈的维护是 O(n) 级的时间复杂度,因为所有元素只会进入栈一次,并且出栈后再也不会进栈了。 没什么好说的,直接上题 提示:点击题目直达题目提交地址 Largest Rectangle in a Histogram 大致题意: 给定从左到右多个矩形,已知这此矩形的宽度都为1,长度不完全相等。 这些矩形相连排成一排,求在这些矩形包括的范围内能得到的面积最大的矩形,输出该面积。 所求矩形可以横跨多个矩形,但不能超出原有矩形所确定的范围。 算法分析&&具体操作: 借助单调性处理问题的思想在于及时排除不可能的选项,保持策略集合的高度有效性和秩序性 对于此题,我们需要从左向右求出每个矩形向右能够扩展到的最大连续宽度 能够扩展的条件:矩形高度大于等于当前矩形高度 那么我们求完之后算出面积取 max 即可 显然朴素算法是不行的,我们需要用到 单调栈 准确的说是: 单调栈维护从起点矩形到当前矩形的高度递增序列 如果当前矩形低于于栈顶矩形,我们就一直将栈中元素弹出,直到遇到低于当前点的矩形,以此来维护栈的递增性

单调队列

匿名 (未验证) 提交于 2019-12-02 23:49:02
最大子序和 输入一个长度为n的整数序列,从中找出一段不超过m的连续子序列,使得整个序列的和最大。 容易想到计算区间和,可以转换成两个前缀和相减,用S[i]表示前i项和,则连续子序列[L,R]中的数的和为S[R]-S[L-1]. 所以原问题转化为找出两个位置x,y,使得s[y]-s[x]最大,且y-x<=M 暴力枚举O(n*m). 首先枚举右端点r,找左端点l,l范围为[r-m,r-1] 注意到: 若k<j<i,且s[k]>=s[j],那么k永远不可能是最佳选择。 以上事实说明,可能成为最佳策略的集合一定是 一个下标位置递增,对应的前缀和S的值也递增的序列。 用队列保存这一队列,随着右端点变,从前向后扫描,对每一个i: 1.判断队头决策与i的距离是否超过M的范围 2.此时队头就是右端点为i时,左端点j的最优选择。 3.不断删除队尾决策,直到队尾对应的S的值小于S[i],然后把i作为一个新的决策入队 //维护单调性,删除的都不可能是最优决策 int l=1,r=1; q[1]=0;//初始决策 j=0; for(int i=1;i<=n;i++){ while(l<=r&&q[l]<i-m)l++;//step1 ans=max(ans,sum[i]-sum[q[l]]);//step2 while(l<=r&&sum[q[r]]>=sum[i])r--;//step3 q[++r]

【优化】单调队列与dp

匿名 (未验证) 提交于 2019-12-02 23:49:02
dp[i][1]=max{dp[j][0]-sum[j]+sum[i]},i-k<=j<i dp[i][1]=max{dp[j][0]-sum[j]}+sum[i]. s[q[head]]-f[q[head]][0]>s[i]-f[i][0]&&head<=tail #include <cstdio> #include <iostream> using namespace std ; long long q [ 2000000 ], f [ 2000000 ][ 2 ]; long long n , k , s [ 2000000 ], a [ 2000000 ]; long long tail , head ; int main (){ scanf ( "%lld%lld" ,& n ,& k ); for ( int i = 1 ; i <= n ;++ i ){ scanf ( "%lld" ,& a [ i ]); s [ i ]= s [ i - 1 ]+ a [ i ]; //sum } tail = head = 1 ; //初始化 for ( int i = 1 ; i <= n ;++ i ){ f [ i ][ 0 ]= max ( f [ i - 1 ][ 0 ], f [ i - 1 ][ 1 ]); //对于不选i,只考虑前面两个即可 while ( q