单调队列+表达式的计算(栈)

学习状况:

最近对什么都失去了兴趣……什么都不想干,小说、漫画、视频都是可有可无,甚至无聊。只能一直看代码。

今天明明看了一上午加下午的1/3,但是只写了两篇解题报告,还有一篇半路夭折,看题解也看不太懂,就变成了一坨草稿。下午看了点栈和单调队列,单链表开了个头,看不懂啊魂淡(虽然单调队列我也迷迷糊糊的,那个求最大最大子序列和的,明明感觉用动态规划我就看得懂,用这个单调队列看的一愣一愣的,网上的题解还都一样!!!互相抄的吗???)

今天训练赛好蓝~嘤嘤嘤,放弃思考ing,也没有大佬,好孤独,听到他们在讨论,我还是很渴望听力好一点的!总之今天又是失败的一天,划水……

本来想试试给博客换个皮肤,失败了!!第一次尝试用代码改点什么,结果失败,不过我那个超级难看的皮肤确实没有了,换成了一个一般难看的而已!!!啊,等我有时间,我要再试试,哼唧!!我就不信这个邪。

表达式的计算

表达式分类:

(1)前缀表达式:就是操作符在前面

(2)中缀表达式:我们一般的数学表达式

(3)后缀表达式:操作符在后面

后缀表达式的计算方法:

①建立一个用于存数据的栈,正向逐一扫描表达式中的元素。

②如果遇到一个数,则把该数直接入栈

③如果遇到一个操作符,则取出栈顶的两个数进行计算,答案放入栈

注意:运算顺序:先弹出的数 操作符 后弹出的数

前缀表达式的计算方法:

反向扫描,其余跟后缀表达式一模一样。

中缀表达式的计算方法(递归看不懂,不整理了):

(1)换成后缀表达式,具体操作为:

①建立一个存储运算符的栈,逐一扫描元素

②遇到数就输出

③遇到左括号。左括号入栈

④遇到右括号,不断取出栈顶并输出,直到栈顶为左括号,然后左括号出栈

⑤遇到运算符,如果栈顶的符号优先级大于等于扫描到的符号,就不断取出栈顶并输出,最后新符号进栈。

⑥依次取出并输出栈中所有剩余的符号,最终序列就是一个后缀表达式

(2)按后缀表达式做法求得答案。

单调栈

常见例题:求最大的矩形面积。

思考

如果矩形单调递增,那么最大的面积就是每个矩形扩展到最右边的宽乘每个矩形的高。

但是如果后面有一个矩形的高度是低于前面的某个矩形的,那么若要扩展到最右边,那些比最右边矮的矩形高的矩形高出去的那一部分将失去作用。所以我们不如将这些高于矮矩形的矩形都删掉,用一个宽度累加,高度为这个矮矩形的高度的矩形替换掉,并且这样不会对后续的计算产生影响,问题可解。

即我们建立一个栈,用来保存若干个矩形,这些矩形的高度是单调递增的->单增队列。

逐一扫描每个矩形,如果当前矩形比栈顶高,直接进栈。否则不断取出比它高的栈顶,累积宽度,用它的高度乘累计的宽度更新答案,然后将高度为它的高度,宽度为累计宽度的矩形入栈,整个扫描结束后,再把所有的矩形依次弹出,按照上面的方法更新答案。

代码

int s[1000];
int ans=0;
int a[n+1]=p=0;
for(int i=1;i<=n;i++)
{
    if(a[i]>s[p])//要加入的这个矩形的高度高于栈顶
    {
        s[++p]=a[i],w[p]=1;//p栈顶的宽度为1,栈顶高度a[i]
    }
    else//低于栈顶
    {
        int width=0;//要累计的宽度
        while(s[p]>a[i])
        {
            width+=w[p];//开始累计
            ans=max(ans,(long long)width*s[p]);//累计的宽度乘以这个矮矩形
            p--;
         }
    s[++p]=a[i];
    w[p]=width+1;
    }
}

队列

元素从右端进入队列(入队),从左端离开队列(出队)。于是我们称队列的左端为队头,右端为队尾。

可以用一个数组和两个变量(记录队头和队尾的位置)来实现队列结构。

由于队列是只能队尾进入,队头一旦空出就不会再有元素进入,时间一长就造成了空间的严重浪费,所以我们经常将其优化为“循环队列”,也就是将一条线的队列优化成首尾相接的环。只要元素的总长度不超过队列的长度,那么队尾就能一直利用历史上曾被利用过的空间。C++里面的queue就是一个循环队列。

队列的变体有:双端都能插入或取出元素的双端队列:C++ stl deque。给每次的元素一个权值,每次取出权值的最大的优先队列:C++ stl priority_queue。

队列是实现广度优先搜索的基础结构!

例题:

(1)Team queue POJ2259

题目大意:n个小组进行排队,每个小组有若干个人,一个人来队伍时,如果看见有自己小组的成员,直接排自己小组成员后面,如果没有,排在队伍末端。给定入队的人的顺序,求出队的顺序。

思考:写n+1个队列,一个队列存储小队的顺序,剩下的n个队列存储小队内部的顺序,输出的时候一个队伍一个队伍的输出。一个队伍输出完了输出下一个小队。

(2)双端队列 BZOJ2457

题目大意:通过若干个双端队列对一个序列进行排序

思考:因为不知道后续数目是哪些,我们做出的局部决策可能导致p、q在一个队列中,后续的介于pq之间的数不管放在哪里都会导致无解。所以我们必须要考虑数值的大小进行排序。

因为要排成一个非降序数列(a[i+1]>=a[i]),所以我们不妨把n个数从小到大排序,然后分成尽少的几段,让每一段对应一个双端队列。

依次取出序列a的下标组成一个新的数组,对a进行排序,b跟着a排序,最后得到一个对应排序后a的下标顺序。

例如:排序前:a[3 6 0 9 6 3],b[1 2 3 4 5 6];排序后:a[0 3 3 6 6 9],b[3 1 6 2 5 4]。

经过分析,如果下标符合单谷性质(先单调递减->从队头进入双端队列,后单调递增->从队尾进入双端队列),那么这一段就可以对应原问题的双端队列,这几个双端队列都是有序的,合起来也是有序的。

还需要注意,如果a里面有相等的数,那么他们的顺序我们可以随意交换,最终目标——分段最少,序号先减后增!

所以我们的算法是,按照a的异同,把排序后的b分成好几个区间,一个区间一个区间对b进行处理,看看能不能持续上一个状态的增减性,不能就开新的增减性,注意从单增到单减,双端序列的个数要+1.

(3)最大子序列和 TYVJ1305

题意:给定一个序列,求出最大的连续子序列的和

思考1:动态规划:一个数字一个数字枚举,看看结果什么时候最大。

代码

#include <iostream>
#include <math.h>
using namespace std;
int n,num[100],sum=0,ans=0;
int main()
{
    while(cin>>n)
    {
        for(int i=1;i<=n;i++)
        {
            cin>>num[i];
        }
        for (int j=1;j<=n;j++)
        {
            sum+=num[j];即使是结果为1,也比从0开始向后加高
            if(sum<0)
            {
                sum = 0;
            }
            ans=max(ans,sum);
        }
        cout<<ans<<endl;
    }
    return 0;
}

思考2:单调队列:转化为两个前缀和相减的形式进行求解:找出两个位置x、y,使s[x]-s[y]最大且y-x<=m。

可能成为最优策略的集合:下标位置递增,对应的前缀和s也递增。

随着右端点从前往后扫描,我们对每个i执行以下操作:

①判断队头决策和i的距离是否超出m,超过就不合法,出队

②此时队头是右端点为i时左端点为j的最优选择(队头是距离i远的那个)

③不断删除队尾决策,直到队尾对应的s值小于s[i](小于s[i]前缀和才能为正数,才有可能最大啊!),然后把i作为一个新的决策入队(队尾距离i进,相对于远的j,更容易最优)

代码:

#include <iostream>
#include <algorithm>
#include <cstring>
#include <cmath>
#include <queue>
#define INF 0x3f3f3f3f
#define LL long long
using namespace std;
const int maxn=300000+7;
int a[maxn],sum[maxn];
int n,m;
int q[maxn];
int main(){
	cin>>n>>m;
	for(int i=1;i<=n;i++)
    {
		cin>>a[i];
		sum[i]=sum[i-1]+a[i];
	}
	int l=1,r=1;
	q[1]=0;
	int ans=0;
	for(int i = 1; i <= n; i++)
	{
		while(l<=r&&q[l]<i-m) l++;//第一步,删掉不符合范围的
		ans=max(ans,sum[i]-sum[q[l]]);//第二步,q[l]是队头坐标
		while(l<=r&&sum[q[r]]>=sum[i]) r--;//第三步,队尾元素
		q[++r] = i;//i变成新的队尾
	}
	cout<<ans<<endl;
    return 0;
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
在Python中,单调单调队列是两种不同的数据结构。单调是一个,它的特点是内的元素是单调的,可以是递增或递减的。在构建单调时,元素的插入和弹出都是在的一端进行的。与此类似,单调队列也是一个队列,它的特点是队列内的元素是单调的,可以是递增或递减的。在构建单调队列时,元素的插入是在队列的一端进行的,而弹出则是选择队列头进行的。 单调队列在解决某些问题时,能够提升效率。例如,滑动窗口最大值问题可以通过使用单调队列来解决。单调队列的结构可以通过以下代码来实现: ```python class MQueue: def __init__(self): self.queue = [] def push(self, value): while self.queue and self.queue[-1 < value: self.queue.pop(-1) self.queue.append(value) def pop(self): if self.queue: return self.queue.pop(0) ``` 上述代码定义了一个名为MQueue的类,它包含一个列表作为队列的存储结构。该类有两个方法,push和pop。push方法用于向队列中插入元素,它会删除队列尾部小于插入元素的所有元素,并将插入元素添加到队列尾部。pop方法用于弹出队列的头部元素。 总结来说,单调单调队列都是为了解决特定问题而设计的数据结构。单调在构建时元素的插入和弹出都是在的一端进行的,而单调队列则是在队列的一端进行的。在Python中,可以通过自定义类来实现单调队列的功能。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值