学习状况:
最近对什么都失去了兴趣……什么都不想干,小说、漫画、视频都是可有可无,甚至无聊。只能一直看代码。
今天明明看了一上午加下午的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;
}