第3章栈和队列 3.1实战题解析 【实战3.1】POJ1363——铁轨问题 视频讲解 时间限制: 1000ms; 内存限制: 65536KB。 问题描述: A市有一个著名的火车站,那里的山非常多。该站建于20世纪,不乐观的是该火车站是一个死胡同,并且只有一条铁轨,如图3.1所示。 当地的传统是从A方向到达的每列火车都继续沿B方向行驶,需要以某种方式进行车厢重组。假设从A方向到达的火车有n(n≤1000)个车厢,按照递增的顺序1,2,…,n编号。火车站负责人必须知道是否可以通过重组得到B方向的车厢序列。 图3.1铁轨问题示意图 输入格式: 输入由若干块组成。除了最后一个块之外,每个块描述了一个列车以及可能更多的车厢重组要求。在每个块的第一行中为上述的整数n,下一行是1,2,…,n的车厢重组序列。每个块的最后一行仅包含0。最后一个块只包含一行0。 输出格式: 输出包含与输入中具有车厢重组序列对应的行。如果可以得到车厢对应的重组序列,输出一行“Yes”,否则输出一行“No”。此外,在输入的每个块之后有一个空行。 输入样例: 5 1 2 3 4 5 5 4 1 2 3 0 6 6 5 4 3 2 1 0 0 输出样例: Yes No Yes 第3章栈和队列 数据结构在线编程实训(C++语言)(全程视频讲解版) 解: 先考虑这样的情况,假设a和b数组中均含n个整数(n为大于1的正整数),都是1~n的某个排列,现在要判断b是否为以a作为输入序列的一个合法出栈序列,即a序列经过一个栈是否得到b的出栈序列。 求解思路是,建立一个整型栈st,用i、j分别遍历a、b序列(初始值均为0),在a序列没有遍历完时循环: ① 将a[i]进栈,i++。 ② 栈不空并且栈顶元素与b[j]相同时循环: 出栈元素e,j++。 在上述过程结束后,如果栈空返回true,表示b序列是a序列的出栈序列,否则返回false,表示b序列不是a序列的出栈序列。 例如,a=[1,2,3,4,5],b=[3,2,1,5,4],i=0,j=0,判断过程如下: ① 初始时栈空,a[0]进栈(i=1)。b[0]≠栈顶元素,a[1]进栈(i=2)。b[0]≠栈顶元素,a[2]进栈(i=3),如图3.2(a)所示。 ② b[0]=栈顶元素,出栈一次,j增1(j=1)。b[1]=栈顶元素,出栈一次,j增1(j=2)。b[2]=栈顶元素,出栈一次,j增1(j=3),如图3.2(b)所示。 ③ 此时栈空,a[3]进栈(i=1)。b[3]≠栈顶元素,a[3]进栈(i=4)。b[3]≠栈顶元素,a[4]进栈(i=5,a序列遍历完毕),如图3.2(c)所示。 ④ b[3]=栈顶元素,出栈一次,j增1(j=4)。b[4]=栈顶元素,出栈一次,j增1(j=5),如图3.2(d)所示。 图3.2判断b=[3,2,1,5,4]是否为出栈序列的过程 此时a序列遍历完毕,栈空返回true,表示b序列是a序列的出栈序列。 又例如,a=[1,2,3],b=[3,1,2],i=0,j=0,判断过程如下: ① 初始时栈空,a[0]进栈(i=1)。b[0]≠栈顶元素,a[1]进栈(i=2)。b[0]≠栈顶元素,a[2]进栈(i=3,a序列遍历完毕),如图3.3(a)所示。 ② b[0]=栈顶元素,出栈一次,j增1(j=1)。b[1]≠栈顶元素,如图3.3(b)所示。 此时a序列遍历完毕,栈不空返回false,表示b序列不是a序列的出栈序列。 图3.3判断b=[3,1,2]是否为出栈序列的过程 再回到本题,实际上就是有多个测试用例,每个测试用例给一个正整数n和若干由1~n排列构成的b序列,判断1,2,…,n作为输入序列经过一个栈是否得到b序列。采用上述思路,直接将a数组用1,2,…,n替代,对应的完整程序如下: #include #include using namespace std; const int MAXN=1005; bool isSerial(int b[],int n)//判断b序列是否为1~n的出栈序列 {stack st; //建立一个栈对象 int i=1,j=0; while (i<=n)//输入序列没有遍历完 {st.push(i); i++; while (!st.empty() && st.top()==b[j]) {st.pop(); //b[j]与栈顶匹配时出栈 j++; } } return st.empty(); //栈空返回true,否则返回false } int main() {int n; int b[MAXN]; while (cin >> n && n)//n=0时输入结束 {while(true)//处理一个块 {cin >> b[0]; if (b[0]==0)//一个块结束 {cout << endl; //输出一个空行 break; } else {for (int i=1;i> b[i]; } if (isSerial(b,n)) cout << "Yes" << endl; else cout << "No" << endl; } } } 上述程序是AC代码,运行时间为176ms,运行占用的空间为391KB,编译语言为C++。 【实战3.2】POJ1208——箱子操作 视频讲解 问题描述参见本书第2章中的实战2.4,这里改为用栈求解。 解: 如图3.4(a)所示为n=4的某个状态表示,box[1]表示位置1的箱子链,实际上箱子链应该是箱子栈,上方是栈顶,当执行move 0 onto 2命令时,将箱子0和2上面的箱子(只有箱子3)放回初始位置,并将0放到2上,结果如图3.4(b)所示。 为此将box数组元素由链表改为栈,即box[i]表示位置i的箱子栈,所有操作执行完毕,借助vector向量输出每个栈中从栈底到栈顶的所有元素即可。 图3.4执行move 0 onto 2操作命令 对应的程序如下: #include #include #include #include using namespace std; const int MAXN=30; stack box[MAXN]; //box[i]表示位置i的箱子栈 stacktmp; //临时栈 int pos[MAXN]; //pos[i]表示编号为i的箱子的位置 void restore(int x)//将x上面的箱子放回初始位置 {int i=pos[x]; //找x所在的位置i while(box[i].top()!=x)//在位置i的箱子栈中从尾部找到x为止 {int t=box[i].top(); //取出箱子t box[t].push(t); //将箱子t添加到位置t的箱子栈中 pos[t]=t; //设置箱子t的位置为t box[i].pop(); //将箱子t从位置i的箱子栈中删除 } } void over(int x,int y)//将箱子x及其上方的箱子放到箱子y所在的箱子栈中 {int i=pos[x]; //找x所在的位置i int j=pos[y]; //找y所在的位置j while(box[i].top()!=x)//在位置i的箱子栈中从尾部找到x为止 {tmp.push(box[i].top()); //将箱子x上方的箱子退出并存放在临时tmp中 box[i].pop(); } box[j].push(box[i].top()); //将箱子x放在位置j的箱子栈中 pos[x]=j; //置箱子x的位置为j box[i].pop(); //从位置i的箱子栈中删除x while(!tmp.empty())//将tmp中的箱子放在x的上方 {box[j].push(tmp.top()); pos[tmp.top()]=j; tmp.pop(); } } int main() {int n; cin >> n; for(int i=0;i> str1 && str1!="quit" ) {cin >> a >> str2 >> b; if(str1=="move" && str2=="onto")//move a onto b {restore(a); restore(b); over(a,b); } else if(str1=="move" && str2=="over")//move a over b {restore(a); over(a,b); } else if(str1=="pile" && str2=="onto")//pile a onto b {restore(b); over(a,b); } else//pile a over b over(a,b); } vector ans; vector::reverse_iterator rit; for(int i=0;i容器qu为队列,模拟栈的操作如下。 图3.5将一个队列作为栈 void push(int x): 元素x进栈和qu进队的操作一致,直接用qu.push(x)实现。 int pop(): 出栈的元素为an-1,将qu中的前n-1个元素出队并立刻进队,再出队列元素an-1并返回该元素。 int top(): 类似pop(),但第n个元素d仍然需要进队,返回d。 bool empty(): 返回队列qu是否为空。 对应的程序代码如下: class MyStack {queue qu; public: MyStack(){}//构造函数 void push(int x)//元素z进栈 { qu.push(x); } int pop()//删除并且返回栈顶元素 {int m=qu.size()-1,d; for(int i=0;i #include using namespace std; void solve(int n)//求解算法 {queue qu; for (int i=1;i<=n;i++)//m个士兵排成一行 qu.push(i); bool flag=true; //是否做1~2报数 while (qu.size()>3)//剩余人数超过3时循环 {if (flag)//1~2报数 {int rest=qu.size(); //剩余人数 int half=rest/2; for (int i=0;i链表容器lst存放一行的n个新兵(编号为1~n),依次报数并删除出列的新兵,直到剩下的人数不超过3人为止,再输出lst中的所有新兵。 参考博客: https://www.cnblogs.com/randylo/p/12607874.html 对应的程序如下: #include #include using namespace std; void solve(int n)//求解算法 {list lst; list::iterator it; for (int i=1;i<=n;i++)//m个士兵排成一行 lst.push_back(i); int m=2; //报数的人数 while (lst.size()>3) {int i=1; it=lst.begin(); while (it!=lst.end()) {if (!(i++%m)) it=lst.erase(it); //删除之后返回下一个位置的指针 else it++; } if (m==2) m=3; else m=2; } it=lst.begin(); //输出最后剩下的士兵 while (it!=lst.end()) {if (it==lst.begin()) printf("%d",*it); else printf(" %d",*it); it++; } printf("\n"); } int main() {int t,n; scanf("%d",&t); //输入测试用例的个数 while (t--) {scanf("%d",&n); //输入一行的人数n solve(n); //求解一个测试用例的问题 } return 0; } 上述程序是AC代码,运行时间为15ms,运行占用的空间为1876KB,编译语言为C++。 【实战3.5】LeetCode84——柱状图中最大的矩形 视频讲解 问题描述: 给定n个非负整数,用来表示柱状图中各个柱子的高度。每个柱子彼此相邻,且宽度为1,求在该柱状图中能够勾勒出来的矩形的最大面积。例如,如图3.6(a)所示的柱状图,其中每个柱子的宽度为1,给定的高度heights[]={2,1,5,6,2,3},求出的最大面积的矩形如图3.6(b)所示,其面积为10。要求设计相应的largestRectangleArea()函数: class Solution { public: int largestRectangleArea(vector& heights) {…} }; 图3.6一个柱状图及其求解结果 解法1: 采用枚举法,对于heights[i..j](j≥i)的j-i+1个柱子,构成的矩形面积为(heights[i..j]中的最小高度)×(j-i+1)。在所有这些矩形面积中比较求最大值。对应的程序如下: class Solution { public: int largestRectangleArea(vector& heights) {if (heights.size()==0) return 0; //处理两种特殊情况 if (heights.size()==1) return heights[0]; int ans=0; //存放最大矩形面积 for (int j=0; j=0; i--)//i从0到j反向遍历 {if (heights[i]heights[j]时一定有maxr([i..j])heights[j]不成立时再求这些面积,从中比较求出最大矩形面积。这样减少了枚举i的次数,从而提高了效率。 为此用一个栈st来维护高度递增的柱子序列(即单调递增栈),栈中存储这些柱子的下标(索引)而不是柱子的高度,每个柱子的下标仅进栈一次,从栈底到栈顶柱子的高度是递增的。用j从0开始遍历heights: 当栈空或者柱子j的高度大于或等于栈顶柱子的高度时,将j进栈并且执行j++,即维护栈中柱子高度的递增性,否则对栈中所有高度大于heights[j]的柱子求矩形面积S。计算过程是退栈柱子tmp,求该柱子到j-1位置为止构成的最大矩形面积,显然其高度是heights[tmp],那么最大矩形的长度length呢?分为两种情况: ① 退栈tmp后栈不空,st.top()为新栈顶柱子,求以柱子j-1结尾、以tmp为最小高度的矩形S的面积,按照单调栈的操作有两种情况,情况1是st.top()=tmp-1(即tmp柱子和栈顶st.top()柱子是相邻的,并且heights[st.top()]& heights) {if (heights.size()==0) return 0; //处理两种特殊情况 if (heights.size()==1) return heights[0]; heights.push_back(0); //在末尾添加一个高度为0的虚柱子 int n=heights.size(); stack st; int ans=0; //存放最大矩形面积,初始为0 for (int j=0;jheights[st.top()]) st.push(j); //栈空或高度是单调递增的,直接进栈 else {while (!st.empty() && heights[st.top()]>=heights[j]) {int tmp=st.top(); st.pop(); //退栈tmp int length; if(st.empty()) length=j; else length=j-st.top()-1; int area=heights[tmp]*length; //求heights[st.top()+1..j-1]的矩形面积 ans=max(ans,area); //维护ans为最大矩形面积 } st.push(j); //j进栈 } } return ans; } }; 上述程序是AC代码,运行时间为12ms,运行占用的空间为15.5MB,编译语言为C++。 【实战3.6】POJ2823——滑动窗口 视频讲解 时间限制: 12000ms; 内存限制: 65536KB。 问题描述: 给出一个长度为n≤106的整数数组。有一个大小为k的滑动窗口,它从数组的最左边移动到最右边,用户只能在窗口中看到k个整数。每次滑动窗口向右移动一个位置。在如表3.1所示的例子中,该数组为{1,3,-1,-3,5,3,6,7},k为3。 表3.1一个k=3的滑动窗口例子 窗口位置 最小值 最大值 [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 确定每个位置的滑动窗口中的最大值和最小值。 输入格式: 输入包含两行,第一行包含两个整数n和k,它们是数组和滑动窗口的长度,第二行有n个整数。 输出格式: 输出中有两行,第一行按从左到右的顺序给出每个位置的窗口中的最小值,第二行为对应的最大值。 输入样例: 8 3 1 3-1 -3 5 3 6 7 输出样例: -1 -3 -3 -3 3 3 3 3 5 5 6 7 解: 设计两个单调队列,minqu为单调递增队列,用于维护序列的最小值(队头为最小元素),maxqu为单调递减队列,用于维护序列的最大值(队头为最大元素)。队列中的每个元素记录对应的整数x和输入的编号i。 由于本题是求k区间内的最大值和最小值,以使用minqu求最小值序列为例,对于当前整数x,只关心它自己和它前面k-1个整数。i标记输入的整数x的编号,当i≤k-1(处理前面的k-1个整数)时不存在区间最小值,直接做进队处理: ① 将队尾≥x的元素从队尾出队。 ② x从队尾进队。 当i≥k(处理第k个整数以及后面的整数)时存在区间最小值,此时除了做上述直接进队的处理外,还需要将队头下标超出k区间范围的元素出队,即将满足i-minqu.front().i>=k的整数从队头出队,目的是保持队头元素为区间内的最小值,再置mina[i]=minqu.front().x取队首元素。 求k区间内的最大值序列与上述过程类似,最后输出最小值序列mina和最大值序列maxa。对应的程序如下: #include #include using namespace std; const int MAXN=1000005; struct Node//队列元素类型 {int x; //元素值 int i; //元素的编号 Node(int x1,int i1):x(x1),i(i1) {} //构造函数 }; dequeminqu; //单调递增队列 dequemaxqu; //单调递减队列 int mina[MAXN]; //存放最小元素序列 int maxa[MAXN]; //存放最大元素序列 int main() {int n,k,x; scanf("%d%d",&n,&k); for (int i=1;i<=n;i++)//按编号输入整数 {scanf("%d", &x); //输入编号为i的整数x while (!minqu.empty() && minqu.back().x>=x)//维护minqu为单调递增队列 minqu.pop_back(); //队尾较大元素出队 minqu.push_back(Node(x,i)); //x从队尾进队 if(i>=k) {while (!minqu.empty() && i-minqu.front().i>=k)//把队头下标超出k区间范围 //的元素出队 minqu.pop_front(); mina[i]=minqu.front().x; //取队首元素 } while (!maxqu.empty() && maxqu.back().x <=x)//维护maxqu为单调递减队列 maxqu.pop_back(); //队尾较小元素出队 maxqu.push_back(Node(x,i)); //x从队尾进队 if (i>=k) {while (!maxqu.empty() && i-maxqu.front().i>=k)//把队头下标超出k区间范围的 //元素出队 maxqu.pop_front(); maxa[i]=maxqu.front().x; //取队首元素 } } for (int i=k;i<=n;i++)//输出最小元素序列 if (i==k) printf("%d",mina[i]); else printf(" %d",mina[i]); printf("\n"); for (int i=k;i<=n;i++)//输出最大元素序列 if (i==k) printf("%d",maxa[i]); else printf(" %d",maxa[i]); printf("\n"); } 上述程序是AC代码,运行时间为9094ms,运行占用的空间为9088KB,编译语言为C++。 3.2在线编程题解析 3.2.1LeetCode150——逆波兰表达式求值 视频讲解 问题描述: 根据逆波兰表示法求表达式的值,有效的运算符包括+、-、*、/。每个运算对象可以是整数,也可以是另一个逆波兰表达式。假设给定的逆波兰表达式总是有效的,换句话说,表达式总会得出有效数值且不存在除数为0的情况。其中整数除法只保留整数部分。 示例1: 输入: ["2", "1", "+", "3", "*"] 输出: 9 解释: ((2+1)*3)=9 示例2: 输入: ["4", "13", "5", "/", "+"] 输出: 6 解释: (4+(13/5))=6 示例3: 输入: ["10", "6", "9", "3", "+", "-11", "*", "/", "*", "17", "+", "5", "+"] 输出: 22 解释: ((10*(6/((9+3)*-11)))+17)+5 =((10*(6/(12 *-11)))+17)+5 =((10*(6/-132))+17)+5 =((10*0)+17)+5 =(0+17)+5 =17+5 =22 设计求逆波兰表达式tokens的值: class Solution { public: int evalRPN(vector& tokens) {…} }; 解: 利用《教程》第3章中3.1.7节求后缀表达式值的思路求解。设计一个运算数栈st,遍历逆波兰表达式tokens,当遇到运算符时出栈两个运算数a和b,做相应运算并将结果进栈,若遇到数字串转换为整数并进栈,tokens处理完毕返回栈顶整数。对应的程序如下: class Solution { public: int evalRPN(vector& tokens) {int a, b; stack st; for (int i=0;i #include using namespace std; typedef pair PA; priority_queue A,B,C; int main() {int n,t=1,x; PA e; char com[10],g; //com存放命令 while(scanf("%d", &n) != EOF && n) {printf("Case #%d:\n", t++); while(!A.empty()) A.pop(); //初始化3个优先队列 while(!B.empty()) B.pop(); while(!C.empty()) C.pop(); int timeid=0; //表示时间戳 while(n--) {scanf("%s %c",com,&g); if(com[1]=='u')//push A x命令 {scanf("%d",&x); if(g=='A') A.push(make_pair(timeid++,x)); else B.push(make_pair(timeid++,x)); } else if(com[1]=='o')//pop A {if(g=='A') {if(!A.empty())//A不空时从A出栈元素 {e=A.top(); A.pop(); printf("%d\n", e.second); } else//A空时从公共栈C出栈元素 {e=C.top(); C.pop(); printf("%d\n", e.second); } } else//B不空时从B出栈元素 {if(!B.empty()) {e=B.top(); B.pop(); printf("%d\n", e.second); } else//B空时从公共栈C出栈元素 {e=C.top(); C.pop(); printf("%d\n", e.second); } } } else//merge A B {char tmp[10]; gets(tmp); //忽略B参数,将A和B中的所有元素合并到C中 while(!A.empty()) {e=A.top(); A.pop(); C.push(e); } while(!B.empty()) {e=B.top(); B.pop(); C.push(e); } } } } return 0; } 运行通过,执行用时为951ms,内存消耗为2632KB,所用语言为C++。 解法2: 同样设置3个栈,A和B为题目中的栈,C为公共栈,存放合并后的结果。每个栈采用数组表示,例如A[0..lena-1]存放栈A中的元素(长度为lena),A[0]为栈底,A[lena-1]为栈顶。由于元素进栈是按时间戳有序的,所以每个栈元素包含元素值x和时间戳timeid数据成员,每个栈从栈底到栈顶如A[0..lena-1]按timeid递增有序,在栈合并操作中采用二路归并将A[0..lena-1]和B[0..lenb-1]合并到C中(如果之前C非空,C[0..lenc-1]一定有序,并且它们的时间戳均早于A和B中所有元素的时间戳)。对应的程序如下: #include using namespace std; const int MAXN=100005; struct Node//栈元素类型 {int x; //元素值 int timeid; //时间戳 Node() {}//构造函数 Node(int x1,int t1):x(x1),timeid(t1) {}//重载构造函数 }; Node A[MAXN],B[MAXN],C[MAXN]; //全局变量: 3个栈 int lena,lenb,lenc; //全局变量: 3个栈的长度 int main() {int n,t=1,x; char com[10],g; //com存放命令 while(scanf("%d", &n) != EOF && n) {printf("Case #%d:\n", t++); lena=lenb=lenc=0; //初始化3个栈 int timeid=0; //表示时间戳 while(n--) {scanf("%s %c",com,&g); if(com[1]=='u')//push A x命令 {scanf("%d",&x); if(g=='A') A[lena++]=Node(x,timeid++); else B[lenb++]=Node(x,timeid++); } else if(com[1]=='o')//pop A {if(g=='A') {if(lena!=0)//A不空时从A出栈元素 {lena--; printf("%d\n", A[lena].x); } else//A空时从公共栈C出栈元素 {lenc--; printf("%d\n", C[lenc].x); } } else {if(lenb!=0)//B不空时从B出栈元素 {lenb--; printf("%d\n", B[lenb].x); } else//B空时从公共栈C出栈元素 {lenc--; printf("%d\n", C[lenc].x); } } } else//merge A B {char tmp[10]; gets(tmp); //忽略B参数,将A和B队列中的所有元素合并到C中 int i=0,j=0; while (i类型的队列qu替代list类型的lst。对应的程序如下: #include #include #include using namespace std; #define MAXN 200005 struct DNode//静态双链表结点类型 {int val; //存放整数 int prior; //前驱位置 int next; //后继位置 } a[MAXN]; //a数组作为静态双链表 int n,m; bool vis[MAXN]; //表示元素是否已删除 int main() {int t; scanf("%d",&t); while (t--) {scanf("%d",&n); memset(vis,false,sizeof(vis)); queue qu; //定义一个队列 vis[0]=vis[n+1]=true; a[0].val=0; a[0].next=1; //a[0]为头结点 a[n+1].val=INT_MAX; for (int i=1;i<=n;i++)//建立双链表 {scanf("%d",&a[i].val); a[i].next=i+1; a[i].prior=i-1; } for (int i=1;i<=n;i++)//将a[i-1]<=a[i]且a[i]>a[i+1]的无序元素进队 if(a[i-1].val<=a[i].val && a[i].val>a[i+1].val) qu.push(i); m=n; while(!qu.empty())//处理无序元素 {int u=qu.front(); //出队元素 qu.pop(); if (vis[u])//a[u]已删除时跳过 continue; if (a[u].val<=a[a[u].next].val)//a[u]正序时跳过 continue; vis[u]=true; //a[u]反序时标记qu中的a[u]被删除 m--; int pos; for (pos=a[u].next;pos<=n;pos=a[pos].next)//依次处理a[u]后面的元素 {if(a[a[pos].prior].val<=a[pos].val)//正序时退出循环 break; vis[pos]=true; //反序时标记qu中的a[pos]被删除 m--; } a[a[u].prior].next=pos; //链表操作:删除a[u]到a[pos]之前的所有结点 a[pos].prior=a[u].prior; qu.push(a[u].prior); //将a[u].prior进队 } printf("%d\n",m); //输出结果 for(int i=a[0].next;i<=n;i=a[i].next) printf("%d ",a[i].val); printf("\n"); } return 0; } 上述程序是AC代码,运行时间为327ms,占用的空间为3120KB,编译语言为C++。 3.2.5HDU4699——编辑器 视频讲解 问题描述参见本书第2章中的2.2.8节,这里采用栈求解。 参考博客: https://www.cnblogs.com/ZhenghangHu/p/9759435.html 解: 设计两个栈,prest栈存放当前光标的前面部分元素,postst存放当前光标的后面部分元素,其他与2.2.8节的思路相同。对应的程序如下: #include #include #include using namespace std; #define MAXN 1000010 int presum[MAXN]; //presum[pos]存放pos位置的前缀和 int maxpresum[MAXN]; //maxpresum[pos]存放pos位置的最大前缀和 stackprest; //存放当前光标之前的序列 stackpostst; //存放当前光标之后的序列 int main() {int n; while (~scanf("%d",&n)) {while(!prest.empty())//清空prest栈 prest.pop(); while(!postst.empty())//清空postst栈 postst.pop(); int pos=0; //当前位置 int len=0; maxpresum[0]=-INT_MAX; presum[0]=0; for(int i=1;i<=n;i++)//处理n个指令 {char op[5]; scanf("%s",op); if (op[0]=='I')//I x指令 {int x; scanf("%d",&x); prest.push(x); //把x进prest栈 pos++; presum[pos]=presum[pos-1]+x; //求pos位置的前缀和 maxpresum[pos]=max(maxpresum[pos-1],presum[pos]); //求pos位置的最大前缀和 } else if (op[0]=='D')//D指令 {prest.pop(); pos--; } else if (op[0]=='L')//L指令 {if (prest.empty()) continue; pos--; int tmp=prest.top(); prest.pop(); postst.push(tmp); } else if (op[0]=='R')//R指令 {if (postst.empty()) continue; pos++; int e=postst.top(); postst.pop(); prest.push(e); presum[pos]=presum[pos-1]+prest.top(); maxpresum[pos]=max(maxpresum[pos-1],presum[pos]); } else if (op[0]=='Q')//Q k指令 {int k; scanf("%d",&k); printf("%d\n",maxpresum[k]); } } } return 0; } 上述程序是AC代码,运行时间为1232ms,占用的空间为15172KB,编译语言为C++。 3.2.6HDU6375——度度熊学队列 视频讲解 时间限制: 1500ms; 内存限制: 131072KB。 问题描述: 度度熊正在学习双端队列,它对双端队列的翻转和合并产生了很大的兴趣。初始时有N个空的双端队列(编号为1~N),用户要支持度度熊的Q次操作。 ① 1 u w val: 在编号为u的队列中加入一个权值为val的元素(w=0表示加在最前面,w=1表示加在最后面)。 ② 2 u w: 询问编号为u的队列中的某个元素并删除它(w=0表示询问并操作最前面的元素,w=1表示询问并操作最后面的元素)。 ③ 3 u v w: 把编号为v的队列“接在”编号为u的队列的最后面。w=0表示顺序接(队列v的开头和队列u的结尾连在一起,队列v的结尾作为新队列的结尾),w=1表示逆序接(先将队列v翻转,再顺序接在队列u的后面)。该操作完成后,队列v被清空。 输入格式: 有多组测试用例,对于每组测试用例,第一行读入两个数N和Q。接下来有Q行,每行3~4个数,意义如上。规定N≤150000,Q≤400000,1≤u,v≤N,0≤w≤1,1≤val≤100000,所有数据的和不超过500000。 输出格式: 对于每个测试用例的每一个操作②,输出一行表示答案。注意,如果操作②的队列是空的,就输出-1且不执行删除操作。 输入样例: 2 10 1 1 1 23 1 1 0 233 2 1 1 1 2 1 2333 1 2 1 23333 3 1 2 1 2 2 0 2 1 1 2 1 0 2 1 1 输出样例: 23 -1 2333 233 23333 参考博客: https://blog.csdn.net/weixin_30274627/article/details/99221872 解: 直接用双端队列deque容器数组dqu来模拟,根据输入的指令编号x执行要求的操作。对应的程序如下: #include #include using namespace std; #define MAXN 150010 dequedqu[MAXN]; int main() {int N,Q; while(~scanf("%d %d",&N,&Q)) {for(int i=0;i<=N;i++) dqu[i].clear(); int x,u,v,w,val; for(int i=0;i #include #include using namespace std; struct QNode {int id; //选手ID int len; //选手跑了路径长度 bool operator < (const QNode & s)const {if (len==s.len)//路径长度相同,ID越小越优先 return id>s.id; else//路径长度不同,其值越大越优先 return len qu[105]; int main() {int t; scanf("%d",&t); int cas=1; while (t--) {int n; scanf("%d",&n); cout << "Case #" << cas++ << ":" << endl; for(int i=0;i> f >> s; //输入f和s p.len=f; p.id=i+1; qu[s].push(p); //所有选手按p速度划分 } for(int i=0;imaxs || (p.len+j*i==maxs && p.id容器qu作为队列,对应的程序如下: #include #include using namespace std; const int MAXN=15; //最大n queuequ; int ans[MAXN]; //存放结果 int main() {int t,n,d; scanf("%d",&t); //测试用例的个数 while(t--) {while (!qu.empty()) qu.pop(); //队列清空 scanf("%d",&n); qu.push(n); for(int i=n-1;i>=1;i--) {qu.push(i); for(int j=1;j<=i;j++) {d=qu.front(); qu.pop(); qu.push(d); } } for(int i=0;i #include #include #include using namespace std; const int MAXN=1000000; //最大成员编号 int termno[MAXN]; //每个成员所属团队的编号 int n; queuetotalqu; //总队列 struct {queue qu; //队列 bool vis; //表示这个团队是否在总队列中 }termqu[1001]; //每个团队队列 void solve(int cas) //求解算法 {cout << "Scenario #" << cas << endl; string com; while (cin >> com && com!="STOP") {if (com=="ENQUEUE")//ENQUEUE p {int p; cin >> p; termqu[termno[p]].qu.push(p); //成员p进入termqu[termno[p]]队列 if (!termqu[termno[p]].vis)//若p是该团队的第一个成员 {totalqu.push(termno[p]); //将该团队编号添加到总队列中 termqu[termno[p]].vis=true; //置该团队在总队列中 } } else if (com=="DEQUEUE")//DEQUEUE {cout << termqu[totalqu.front()].qu.front() << endl; termqu[totalqu.front()].qu.pop(); //总队列中的第一个团队出队 if (termqu[totalqu.front()].qu.empty())//该团队为空的情况 {termqu[totalqu.front()].vis=false; totalqu.pop(); } } else break; //STOP } } void init() //初始化 {memset(termno,0,sizeof(termno)); //成员所属团队清0 while(!totalqu.empty())//总队列清空 totalqu.pop(); for(int i=1;i<=n;i++)//每个团队队列清空 {while(!termqu[i].qu.empty()) termqu[i].qu.pop(); termqu[i].vis=false; } } int main() {int cas=1; //测试用例编号 int t,p; while(cin >> n && n != 0)//输入n(团队个数) {init(); for(int i=1;i<=n;i++)//输入n个团队 {scanf("%d",&t); //输入团队人数t for(int j=1;j<=t;j++)//输入t个成员 {scanf("%d",&p); termno[p]=i; } } solve(cas++); //求解 cout << endl; } return 0; } 上述程序是AC代码,运行时间为469ms,运行占用的空间为4544KB,编译语言为C++。 3.2.10POJ2559——最大矩形面积 视频讲解 时间限制: 1000ms; 内存限制: 65536KB。 问题描述: 与本书中的实战3.5求柱状图的最大矩形面积类似。 输入格式: 输入包含几个测试用例。每个测试用例描述一个直方图,并以整数n(1≤n≤100000)开头,表示它所组成的矩形个数,然后跟随n个整数,即h1,h2,…,hn(0≤hi≤1000000000),这些整数表示从左到右顺序的直方图矩形的高度,每个矩形的宽度为1。以输入n为0结束。 输出格式: 对于每个测试用例输出一行,指定直方图中最大矩形的面积。 输入样例: 7 2 1 4 5 1 3 3 4 1000 1000 1000 1000 0 输出样例: 8 4000 解: 采用单调栈st,ans表示最大矩形面积(初始为0),求解思路参见本书中实战3.5的解法2,这里有两点修改。 (1) 在所有柱子高度输入完毕后,再对单调栈中的所有柱子做一次计算,而不是通过添加0高度的柱子自动触发计算。 (2) 每个柱子都进栈一次、出栈一次,栈中的每个柱子除了高度h成员外还带一个len成员表示该柱子对应矩形的宽度,在进栈和出栈过程中累加当前柱子的宽度,在出栈中求对应矩形的面积,比较求出ans。 以输入柱子高度heights[]={2,1,5,6,2,3}为例: ① i=0,[2,1]进栈([2,1]中的前者2为高度,后者1为该柱子当前对应矩形的宽度)。st=([2,1])。 ② i=1,出栈[2,1],tmp=0,栈顶柱子对应的宽度=1+tmp=1(出栈时tmp表示当前柱子的后面宽度),求出面积=2×1=2。置tmp=1,将[1,1+tmp]=[1,2]进栈(进栈时tmp表示当前柱子的前面宽度)。st=([1,2])。 ③ i=2,[5,1]进栈,st=([1,2],[5,1])。 ④ i=3,[6,1]进栈,st=([1,2],[5,1],[6,1])。 ⑤ i=4,出栈[6,1],tmp=0,栈顶柱子对应的宽度=1+tmp=1,求出面积=6×1=6。置tmp=1,出栈[5,1],栈顶柱子对应的宽度=1+tmp=2,求出面积=5×2=10。置tmp=2,将[2,1+tmp]=[2,3]进栈。st=([1,2],[2,3])。 ⑥ i=5,[3,1]进栈。st=([1,2],[2,3],[3,1])。 ⑦ 最后计算,tmp=0,出栈[3,1],栈顶柱子对应的宽度=1+tmp=1,求出面积=3×1=3。置tmp=1,出栈[2,3],栈顶柱子对应的宽度=3+tmp=4,求出面积=2×4=8。置tmp=4,出栈[1,2],栈顶柱子对应的宽度=2+tmp=6,求出面积=1×6=6。栈空结束。 得到最大面积=10。对应的程序如下: #include #include using namespace std; struct Node//栈元素类型 {long long h; //柱子的高度 long long len; //当前柱子高度的矩形宽度 }; stackst; int main() {long long tmp,ans,area,n; Node q; while (scanf("%lld",&n)!=EOF) {if(n==0) break; ans=0; for (int i=0;ians) ans=area; //求最大矩形面积 tmp=st.top().len; st.pop(); } q.len+=tmp; st.push(q); } else st.push(q); } tmp=0; while (!st.empty())//输入完毕做最后一次计算 {st.top().len+=tmp; area=st.top().h*st.top().len; if (area>ans) ans=area; tmp=st.top().len; st.pop(); } cout << ans << endl; } } 上述程序是AC代码,运行时间为360ms,运行占用的空间为1304KB,编译语言为C++。 3.2.11POJ3984——迷宫问题 视频讲解 时间限制: 1000ms; 内存限制: 65536KB。 问题描述: 定义一个二维数组。 int maze[5][5] = { 0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 0, } 它表示一个迷宫,其中的1表示墙壁,0表示可以走的路,注意只能横着走或竖着走,不能斜着走,要求编程找出从左上角到右下角的最短路线。 输入格式: 一个5×5的二维数组,表示一个迷宫。数据要保证有唯一解。 输出格式: 从左上角到右下角的最短路径,格式如样例所示。 输入样例: 0 1 0 0 0 0 1 0 1 0 0 0 0 0 0 0 1 1 1 0 0 0 0 1 0 输出样例: (0, 0) (1, 0) (2, 0) (2, 1) (2, 2) (2, 3) (2, 4) (3, 4) (4, 4) 解法1: 由于求的是最短路径,采用队列求迷宫问题的方法,与《教程》中3.2.7节的原理完全相同。在输出迷宫路径时,通过一个path数组将反向路径反向输出即得到正向路径。对应的程序如下: #include #include #include using namespace std; const int MAX=5; //迷宫的最大行、列数 int dx[]={-1,0,1,0}; //x方向的偏移量 int dy[]={0,1,0,-1}; //y方向的偏移量 int mg[MAX][MAX]; int m=5,n=5; struct Box//队列中的方块元素类型 {int i; //方块的行号 int j; //方块的列号 Box *pre; //本路径中上一方块的地址 Box() {}//构造函数 Box(int i1,int j1)//重载构造函数 {i=i1; j=j1; pre=NULL; } }; void disppath(queue &qu)//输出一条迷宫路径 {vector apath; //存放一条迷宫路径 Box *b; b=qu.front(); //从队头开始向入口方向搜索 while (b!=NULL) {apath.push_back(Box(b->i,b->j)); //将搜索的方块添加到apath中 b=b->pre; } for (int i=apath.size()-1;i>=0;i--)//反向输出构成一条正向迷宫路径 printf("(%d, %d)\n",apath[i].i,apath[i].j); } void mgpath(int xi,int yi,int xe,int ye)//求一条从(xi,yi)到(xe,ye)的迷宫路径 {Box *b,*b1; queuequ; //定义一个队列qu b=new Box(xi,yi); //建立入口的对象b qu.push(b); //入口对象b进队,其pre置为NULL mg[xi][yi]=-1; //为避免来回找相邻方块置mg值为-1 while (!qu.empty())//队不空时循环 {b=qu.front(); //取队头方块b if (b->i==xe && b->j==ye)//找到了出口,输出路径 {disppath(qu); //输出一条迷宫路径 return; } qu.pop(); //出队方块b for (int di=0;di<4;di++)//循环扫描每个方位,把每个可走的方块进队 {int i=b->i+dx[di]; //找b的di方位的相邻方块(i,j) int j=b->j+dy[di]; if (i>=0 && i=0 && jpre=b; //将该相邻方块进队,并置其pre为前驱方块 qu.push(b1); mg[i][j]=-1; //为避免来回找相邻方块置mg值为-1 } } } } int main() {for (int i=0;i #include #include using namespace std; const int MAX=5; //迷宫最大的行、列数 int dx[]={-1,0,1,0}; //x方向的偏移量 int dy[]={0,1,0,-1}; //y方向的偏移量 int mg[MAX][MAX]; int m=5,n=5; struct Box//队列中的方块元素类型 {int i; //方块的行号 int j; //方块的列号 Box() {}//构造函数 Box(int i1,int j1)//重载构造函数 {i=i1; j=j1; } }; Box pre[MAX][MAX]; //表示前驱关系 void disppath(int xi,int yi,int xe,int ye)//输出一条迷宫路径 {vector apath; //存放一条迷宫路径 int i,j,i1,j1; i=xe; j=ye; //从(xe,ye)反推逆路径 while (i!=xi || j!=yi) {apath.push_back(Box(i,j)); //将搜索的方块添加到apath中 i1=pre[i][j].i; j1=pre[i][j].j; i=i1; j=j1; } apath.push_back(Box(xi,yi)); //将入口添加到apath中 for (int i=apath.size()-1;i>=0;i--)//反向输出构成一条正向迷宫路径 printf("(%d, %d)\n",apath[i].i,apath[i].j); } void mgpath(int xi,int yi,int xe,int ye)//求一条从(xi,yi)到(xe,ye)的迷宫路径 {Box b,b1; queuequ; //定义一个队列qu b=Box(xi,yi); //建立入口的对象b qu.push(b); //入口对象b进队 mg[xi][yi]=-1; //为避免来回找相邻方块置mg值为-1 while (!qu.empty())//队不空时循环 {b=qu.front(); //取队头方块b if (b.i==xe && b.j==ye)//找到了出口,输出路径 {disppath(xi,yi,xe,ye); //输出一条迷宫路径 return; } qu.pop(); //出队方块b for (int di=0;di<4;di++)//循环扫描每个方位,把每个可走的方块进队 {int i=b.i+dx[di];//找b的di方位的相邻方块(i,j) int j=b.j+dy[di]; if (i>=0 && i=0 && j #include #include #include using namespace std; #define MAXN 90 int priority(char c)//求运算符c的优先级 {if(c=='(') return 0; else if(c=='*') return 2; else return 1; } void convert(char *exp,char *postexp)//转换为后缀表达式 {int len=strlen(exp),t=0; char ch; stack st; //定义一个栈 for(int i=0;i='a') || (ch>='0' && ch<='9')) postexp[t++]=ch; else {if (st.empty() || ch=='(') st.push(ch); else if (ch==')') {while (!st.empty() && st.top()!='(') {postexp[t++]=st.top(); st.pop(); } st.pop(); } else {while (!st.empty() && priority(ch)<=priority(st.top())) {postexp[t++]=st.top(); st.pop(); } st.push(ch); } } } while (!st.empty()) {postexp[t++]=st.top(); st.pop(); } postexp[t]='\0'; } int calculate(char *postexp)//求后缀表达式值 {int len=strlen(postexp),a,b,c; char ch; stack st; for(int i=0; i='0' && ch<='9') st.push(ch-'0'); else if (ch<='z' && ch>='a') st.push(int(ch)); else {a=st.top(); st.pop(); b=st.top(); st.pop(); switch(ch) { case '*': c=b*a; break; case '+': c=b+a; break; case '-': c=b-a; break; } st.push(c); } } return st.top(); } int main() {char exp[MAXN],postexp[MAXN]; int n; scanf("%d",&n); getchar(); while(n--) {gets(exp); convert(exp,postexp); int ans1=calculate(postexp); gets(exp); convert(exp,postexp); int ans2=calculate(postexp); if(ans1==ans2) printf("YES\n"); else printf("NO\n"); } } 上述程序是AC代码,运行时间为0ms,运行占用的空间为104KB,编译语言为C++。