目录
DAY 5 单调队列/栈
训练地址:传送门
训练情况简介
早上:A、B、C、D题
下午:E题(未出,看了题解)、F题(暂时没有思路)
晚上:牛客小白月赛75+F、G题
6.30 记
今天仅做了单调栈的题,再练了一下相关题
后面的单调队列有两道都做过了,所以后面有时间记得补
A 题
题意:
给定一个数列,判断每个元素后第一个大于当前元素的下标,没有则为0
思路:
递减单调栈模板题
B 题
题意:
给定一个序列,代表每一只奶牛的高度,判断每一只奶牛在看到比自己高的奶牛前的奶牛数,最后输出总数
思路:
依旧是递减单调栈,与A题类似,但没有那么直接,得自己翻译一下题目
C 题
题意:
给你一个长度为n的序列,让你确定一个区间\([l,r]\),满足\(1\leq l<r\leq n\)时,该区间内的最大值和次大值的异或和最大
思路:
这题有点妙哈,但也让我理解了单调栈的妙处!利用递减单调栈快速寻找区间的最大值和次大值
我们维护一个递减的单调栈,从前往后遍历。在出栈时,形成了一个序列,且当前元素为最大,栈顶元素为次大,ans统计结果的最大值;在进栈时,也形成了一个序列,当前为次大,栈顶为最大,更新异或最大值。最后输出答案即可。
总之,妙!这比\(O(n^3)\)寻找区间和区间的最大次大爽快多了
D 题
题意:
给你一个长为n的序列,让你确定是否对于所有区间\([l,r]\),满足\(1\leq l<r\leq n\)时,该区间内的最大值大于等于该区间的和
思路:
依旧是利用递减单调栈存最大值,栈内存序列下标,每次进栈出栈都更新最大值与区间和的大小关系,即可得到答案
这里有个小问题,为什么仅考虑两侧即可?就是为什么进栈出栈的时候考虑就行,那不是有些序列(栈维护最大值递减,非递减则会出栈相应元素)没有考虑到吗?就是为什么仅考虑一个最大值的单左侧是否成立,单右侧是否成立即可?因为,除了当前元素,单左侧的元素满足和\(\leq0\)(因为当前元素值大于单左侧的和),对于单右侧元素同理,故对于整个左右合并,除了当前元素的和仍小于0,故条件成立,满足题意
而且,从前面往后遍历数组,满足所有最大值的单左侧成立,之后在进栈的时候维护单右侧,使得单右侧依然成立,故整个栈都是成立的。
这题还可以再理解理解,写完代码后才发现里面的妙处!
E 题 未出,题解补
题意:
给你一个长度为n的数组,对于所有区间\([l,r]\),满足\(1\leq l<r\leq n\),求解\(\sum_{l=1}^{n-1}{\sum_{r=l+1}^{n}{次大值in [l,r] }}\)
思路:
主要就是对于每一个元素,我们都要考虑其对于最终式子的贡献,ans加上该点的贡献,即可得到最终答案
那么问题来了,怎么快捷地找一个区间的次小值,对于单调栈来说,找区间的最大值,次大值很容易,但是对于所有区间的最小值,次小值,就很吃力了应该,所以单单依靠一个单调栈估计很难求解
自己写的双向链表没法指向第二个大于自己的,,,或者说,想错了
洛谷的题解给了两种做法
做法一:单调栈+st表
很妙!
就是利用单调栈找到当前元素左右最近比自己最大的值,再利用两个st表快速找到左边和右边第二个最近的比当前元素大的数的位置,在统计求解即可
待学习st表和代码实现 实现后打勾 [ ]
做法二:双向链表
很妙!
利用双向链表实现上面思路的st表,直接在代码里给出注释
//>>>Qiansui
#include<map>
#include<set>
#include<list>
#include<stack>
#include<cmath>
#include<queue>
#include<deque>
#include<cstdio>
#include<string>
#include<vector>
#include<utility>
#include<iomanip>
#include<cstring>
#include<cstdlib>
#include<iostream>
#include<algorithm>
#include<functional>
#define ll long long
#define ull unsigned long long
#define mem(x,y) memset(x,y,sizeof(x))
#define debug(x) cout << #x << " = " << x << endl
#define debug2(x,y) cout << #x << " = " << x << " " << #y << " = "<< y << endl
//#define int long long
inline ll read()
{
ll x=0,f=1;char ch=getchar();
while (ch<'0'||ch>'9'){if (ch=='-') f=-1;ch=getchar();}
while (ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+ch-48;ch=getchar();}
return x*f;
}
using namespace std;
typedef pair<int,int> pii;
typedef pair<ll,ll> pll;
typedef pair<ull,ull> pull;
typedef pair<double,double> pdd;
/*
difficult...
思路来自洛谷,还在理解中,待摘记
这个从头开始的双向链表
前面的结果递推到后面
妙!
*/
const int maxm=1e5+5,inf=0x3f3f3f3f,mod=998244353;
ll n,a[maxm],L[maxm],R[maxm];
void solve(){
cin>>n;
ll t;
for(int i=1;i<=n;++i){
cin>>t;
//对于每个数统计其位于数组中的位置
a[t]=i;
//其初始左右即标记为数组中位置的左右,后面会更新
L[i]=i-1;
R[i]=i+1;
}
ll ans=0;
for(int i=1;i<=n;++i){//从小到大遍历数,同时更新L和R
//l、r即为左右最近的比当前数大的下标
int l=L[a[i]],r=R[a[i]];
//左边存在区间时,必取左大1和当前,当前为区间次大值
//再向两边扩展,两边扩展小于当前数的数,可能的区间数即为(左大1-左大2)*(右大-当前)
if(l>=1) ans+=i*(l-L[l])*(r-a[i]);
//右边存在区间时,必取右大1和当前,当前为区间次大值
//再向两边扩展,两边扩展小于当前数的数,可能的区间数即为(右大2-右大1)*(当前-左大)
if(r<=n) ans+=i*(R[r]-r)*(a[i]-l);
//删去当前数的影响,因为它不可能成为接下来区间的次小数
L[r]=l;R[l]=r;
}
cout<<ans<<'\n';
return ;
}
signed main(){
ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
int _=1;
// cin>>_;
while(_--){
solve();
}
return 0;
}
相关资料:
妙哉!Second Sum! - Acfboy 的博客 - 洛谷博客 (luogu.com.cn)
F 题 未出,题解补
题意:
给你一个长度为n的数组m,要求你再构造一个数组a,满足两个条件:
\(1. 对于所有的i\in[1,n],有a_i\leq m_i\)
\(2. 满足1的情况下,对于所有的j<i<k\in[1,n],a数组中不存在a_j>a_i<a_k\)
求在这两个条件下,数组和的最大值?
思路:
题目的意思是,对于你自己构造的数组,不能出现"V形"的样子,肯定是"倒V形"
那么最终可能的数组情况就只有三种情况:单调非递减、单调非递增、中间凸两边凹三种情况
所以我们可以利用单调栈来找前两种情况的最优值,同时记下前缀和后缀和,这是为了方便第三种情况的判断,具体可见代码
//>>>Qiansui
#include<map>
#include<set>
#include<list>
#include<stack>
#include<cmath>
#include<queue>
#include<deque>
#include<cstdio>
#include<string>
#include<vector>
#include<utility>
#include<iomanip>
#include<cstring>
#include<cstdlib>
#include<iostream>
#include<algorithm>
#include<functional>
#define ll long long
#define ull unsigned long long
#define mem(x,y) memset(x,y,sizeof(x))
#define debug(x) cout << #x << " = " << x << endl
#define debug2(x,y) cout << #x << " = " << x << " " << #y << " = "<< y << endl
//#define int long long
inline ll read()
{
ll x=0,f=1;char ch=getchar();
while (ch<'0'||ch>'9'){if (ch=='-') f=-1;ch=getchar();}
while (ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+ch-48;ch=getchar();}
return x*f;
}
using namespace std;
typedef pair<int,int> pii;
typedef pair<ll,ll> pll;
typedef pair<ull,ull> pull;
typedef pair<double,double> pdd;
/*
wa一发,思路有问题
题目好像还看错了!j < i < k是对于所有的下标的,而不是局部
那么最终的序列的可能情况:
单调非递增或非递减、中间凸两边凹三种情况
*/
const int maxm=5e5+5,inf=0x3f3f3f3f,mod=998244353;
ll n,a[maxm],l[maxm],r[maxm],ans[maxm];
ll suml[maxm],sumr[maxm];
void solve(){
cin>>n;
for(int i=1,j;i<=n;++i){
cin>>a[i];
//利用数组模拟单调栈
j=i-1;
while(j>0 && a[i]<a[j]){
j=l[j];
}
l[i]=j;
//更新前缀和
suml[i]=suml[j]+1ll*(i-j)*a[i];//a[j+1~i]=a[i]
}
ll s=0,t;
for(int i=n,j;i>0;--i){
//利用数组模拟单调栈
j=i+1;
while(j<=n && a[i]<a[j]) j=r[j];
r[i]=j;
//更新后缀和
sumr[i]=sumr[j]+1ll*(j-i)*a[i];//a[i~j-1]=a[i]
//维护最大值,判断第三种情况的成立性
if(suml[i]+sumr[i]-a[i]>s){
s=suml[i]+sumr[i]-a[i];
t=i;
}
}
for(int i=t-1;i>0;--i){//从t向右维护数组a
if(a[i]>a[i+1]) a[i]=a[i+1];
}
for(int i=t+1;i<=n;++i){//从t向左维护数组a
if(a[i]>a[i-1]) a[i]=a[i-1];
}
for(int i=1;i<=n;++i){
cout<<a[i]<<" \n"[i==n];
}
return ;
}
signed main(){
ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
int _=1;
// cin>>_;
while(_--){
solve();
}
return 0;
}
相关资料:
题解传送门
G 题 看了cf数据,得wa启发
题意:
n个数依次排列,玩家初始位于第一个数上,玩家想抵达最后一个数上,但玩家的移动不是随意的,需要满足三个条件之一,才可以在第 i 和 j 个数上移动 (i < j)
三个条件为:
$1.i+1=j $
\(2. max(h_{i+1},...,h_{j-1})<min(h_i,h_j)\)
\(3. min(h_{i+1},...,h_{j-1})>max(h_i,h_j)\)
题目问你,玩家若最终抵达,最小的移动次数是多少?
思路:
注意不等号是严格不等号,又因为这个题在单调栈,所以判断移动时得注意相同数的影响
一个数组DP统计到达第 i 个数时所需要的最小移动次数
想要移动次数最小,就要充分利用2、3条件
对于第 i 个数,我们可以寻找其前面第一个大于/等于/小于的数,这三个数都可以作为移动的起点,之后利用这些起点更新第 i 个位置的移动最小值,从而递推实现整体最小
最终的答案即为\(dp[n]\)
wa5可能是因为移动的考虑不充分
wa13可能就是重复数的影响
补充内容
单调栈 Monotone Stack
【图解单调栈】两种方法,两张图秒懂
https://leetcode.cn/problems/next-greater-node-in-linked-list/solution/tu-jie-dan-diao-zhan-liang-chong-fang-fa-v9ab/
举例:返回每个元素两侧严格大于它的元素位置(不存在则为 -1 或 n)
如何理解:把数组想象成一列山峰,站在 a[i] 的山顶仰望两侧的山峰,是看不到高山背后的矮山的,只能看到一座座更高的山峰
这就启发我们引入一个底大顶小的单调栈,入栈时不断比较栈顶元素直到找到一个比当前元素大的
技巧:事先压入一个边界元素到栈底,这样保证循环时栈一定不会为空,从而简化逻辑
一些转换:
若区间 [l,r] 的最大值等于 a[r],则 l 必须 > left[r]
若区间 [l,r] 的最大值等于 a[l],则 r 必须 < right[l]
这一结论可以用于思考一些双变量的题目
https://oi-wiki.org/ds/monotonous-stack/
https://cp-algorithms.com/data_structures/stack_queue_modification.html
单调栈
- 496. 下一个更大元素 I(单调栈模板题)
- 503. 下一个更大元素 II
- 2454. 下一个更大元素 IV
- 456. 132 模式
- 739. 每日温度
- 901. 股票价格跨度
- 1019. 链表中的下一个更大节点
- 1124. 表现良好的最长时间段
- 1475. 商品折扣后的最终价格
- 2289. 使数组按非递减顺序排列
矩形系列
字典序最小
贡献法
- 907. 子数组的最小值之和
- 1856. 子数组最小乘积的最大值
- 2104. 子数组范围和
- 2281. 巫师的总力量和
模板题
https://www.luogu.com.cn/problem/P5788
https://www.luogu.com.cn/problem/P2866 http://poj.org/problem?id=3250
NEERC05,UVa 1619 https://onlinejudge.org/index.php?option=com_onlinejudge&Itemid=8&category=825&page=show_problem&problem=4494
转换 https://codeforces.com/problemset/problem/280/B
转换 LC2289 https://leetcode.cn/problems/steps-to-make-array-non-decreasing/
max >= sum https://codeforces.com/problemset/problem/1691/D
LC1124 https://leetcode.cn/problems/longest-well-performing-interval/
你从单调栈学到了什么思想?LC1944 https://leetcode.cn/problems/number-of-visible-people-in-a-queue/
下下个最大元素 LC2454 https://leetcode.cn/problems/next-greater-element-iv/ - 应用 https://atcoder.jp/contests/abc140/tasks/abc140_e
max(最小值*子数组和) LC1856 https://leetcode.cn/problems/maximum-subarray-min-product/
字典序最小
LC316 https://leetcode.cn/problems/remove-duplicate-letters/ - 扩展:重复个数不超过 limit https://leetcode.cn/contest/tianchi2022/problems/ev2bru/
LC402 https://leetcode.cn/problems/remove-k-digits/
LC321 https://leetcode.cn/problems/create-maximum-number/
计算贡献(所有子数组的……的和)
最小值 LC907 https://leetcode.cn/problems/sum-of-subarray-minimums/
最大值-最小值 LC2104 https://leetcode.cn/problems/sum-of-subarray-ranges/
最小值*和 LC2281 https://leetcode.cn/problems/sum-of-total-strength-of-wizards/
第二大 https://atcoder.jp/contests/abc140/tasks/abc140_e
与 DP 结合
https://codeforces.com/problemset/problem/5/E
https://codeforces.com/problemset/problem/1313/C2
https://codeforces.com/problemset/problem/1407/D
结合线段树,或者巧妙地在单调栈中去维护最值 https://codeforces.com/problemset/problem/1483/C
按照最大值分类讨论 LC1335 https://leetcode.cn/problems/minimum-difficulty-of-a-job-schedule/
LC2355 https://leetcode.cn/problems/maximum-number-of-books-you-can-take/
其他
LC42 接雨水 https://leetcode-cn.com/problems/trapping-rain-water/
评注:接雨水有三种不同的解法(DP、单调栈和双指针),其中双指针是 DP 的空间优化写法,讲解见 https://www.bilibili.com/video/BV1Qg411q7ia/
本质上是两种计算策略:计算每个下标处的接水量(纵向累加),计算一段高度对应的接水宽度(横向累加)
LC84 柱状图中最大的矩形 https://leetcode-cn.com/problems/largest-rectangle-in-histogram/ http://poj.org/problem?id=2559 http://poj.org/problem?id=2082
LC85 最大全 1 矩形(实现见下面的 maximalRectangleArea)https://leetcode-cn.com/problems/maximal-rectangle/ 原题为 http://poj.org/problem?id=3494
LC1504 全 1 矩形个数(实现见下面的 numSubmat)https://leetcode-cn.com/problems/count-submatrices-with-all-ones/
LC768 https://leetcode.cn/problems/max-chunks-to-make-sorted-ii/
后缀数组+不同矩形对应方案数之和 https://codeforces.com/edu/course/2/lesson/2/5/practice/contest/269656/problem/D
与 bitOpTrickCnt 结合(见 bits.go)https://codeforces.com/problemset/problem/875/D
已知部分 right 还原全部 right;已知 right 还原 a https://codeforces.com/problemset/problem/1158/C
单调队列 Monotone Queue
两张图秒懂单调队列(Python/Java/C++/Go)
https://leetcode.cn/problems/shortest-subarray-with-sum-at-least-k/solution/liang-zhang-tu-miao-dong-dan-diao-dui-li-9fvh/
需要不断维护队列的单调性,时刻保证队列元素从大到小或从小到大
前置知识:双指针
以固定窗口大小的区间最大值为例(此时维护的是一个从大到小的单调队列):
每次向右移动一格左指针,在移动前,如果左指针指向的元素在队首左侧,说明左指针指向的元素小于队首,移动左指针不会改变区间最大值,直接移动左指针即可;
如果左指针指向的就是队首,那么移动左指针会使区间最大值变小(变为单调队列队首之后的那个元素),我们要弹出队首。
这样无论是何种情况,都保证了在移动左指针后,单调队列的队首始终为当前区间的最大值。
https://oi-wiki.org/ds/monotonous-queue/
https://oi-wiki.org/dp/opt/monotonous-queue-stack/
https://cp-algorithms.com/data_structures/stack_queue_modification.html
https://blog.csdn.net/weixin_43914593/article/details/105791217 算法竞赛专题解析(13):DP优化(3)--单调队列优化
todo https://xyzl.blog.luogu.org/DQ-OP-DP
- 面试题 59-II. 队列的最大值(单调队列模板题)
- 239. 滑动窗口最大值
- 862. 和至少为 K 的最短子数组
- 1438. 绝对差不超过限制的最长连续子数组
https://leetcode.cn/tag/monotonic-queue/problemset/
单调队列优化 DP
todo https://www.luogu.com.cn/problem/P2627
http://judge.u-aizu.ac.jp/onlinejudge/description.jsp?id=1070
老鼠进洞 http://codeforces.com/problemset/problem/797/F
LC375 猜数字大小 II https://leetcode-cn.com/problems/guess-number-higher-or-lower-ii/
https://leetcode.cn/problems/guess-number-higher-or-lower-ii/solution/cong-ji-yi-hua-sou-suo-on3-dao-dong-tai-q13g9/