《算法竞赛进阶指南》续篇~
原题链接:
https://www.acwing.com/problem/content/90/
题目描述
设计一个支持push,pop,top等操作并且可以在O(1)时间内检索出最小元素的堆栈。
push(x)–将元素x插入栈中
pop()–移除栈顶元素
top()–得到栈顶元素
getMin()–得到栈中最小元素
样例
MinStack minStack = new MinStack();
minStack.push(-1);
minStack.push(3);
minStack.push(-4);
minStack.getMin(); --> Returns -4.
minStack.pop();
minStack.top(); --> Returns 3.
minStack.getMin(); --> Returns -1.
开两个栈,一个存原栈内容,一个存最小栈,然后进行栈的具体操作
问题就在于如何维护最小栈。
例如栈:4 5 6 3 8 9 1 2
最小栈:4 4 4 3 3 3 1 1
我们可以发现维护最小栈的条件是:当i>j且stack[i]>stack[j],那么将stack[j]加入到min_stack[j]里面,否则min_stack[j]=stack[i].
class MinStack {
public:
stack<int> stk,min_stk;
/** initialize your data structure here. */
MinStack() {
}
void push(int x) {
stk.push(x);
if(min_stk.empty()||x<=min_stk.top()) min_stk.push(x);
}
void pop() {
if(stk.top()==min_stk.top()) min_stk.pop();
stk.pop();
}
int top() {
return stk.top();
}
int getMin() {
return min_stk.top();
}
};
/**
* Your MinStack object will be instantiated and called as such:
* MinStack obj = new MinStack();
* obj.push(x);
* obj.pop();
* int param_3 = obj.top();
* int param_4 = obj.getMin();
*/
原题链接:
https://www.acwing.com/problem/content/130/
题目描述
你将要实现一个功能强大的整数序列编辑器。
在开始时,序列是空的。
编辑器共有五种指令,如下:
1、I x,在光标处插入数值 x。
2、D,将光标前面的第一个元素删除,如果前面没有元素,则忽略此操作。
3、L,将光标向左移动,跳过一个元素,如果左边没有元素,则忽略此操作。
4、R,将光标向右移动,跳过一个元素,如果右边没有元素,则忽略次操作。
5、Q k,假设此刻光标之前的序列为 a1,a2,…,an,输出 max1≤i≤kSi,其中 Si=a1+a2+…+ai。
输入格式
第一行包含一个整数 Q,表示指令的总数。
接下来 Q 行,每行一个指令,具体指令格式如题目描述。
输出格式
每一个 Q k 指令,输出一个整数作为结果,每个结果占一行。
数据范围
1≤Q≤106,
|x|≤103,
1≤k≤n
输入样例:
8
I 2
I -1
I 1
Q 3
L
D
R
Q 2
输出样例:
2
3
这是一道对顶栈问题。什么意思?
仔细分析一下一下题目,这道题的所有操作都是围绕着光标进行的,光标的左移右移和删除,完全可以看作是两个对顶栈的相互入栈和出栈,如图所示:
对于第五个操作,就是求从1到光标处每一个值的前缀和的最大值是多少,每次操作后记录每个下标的最大前缀和就可以了,按题意,我们只需要求左边的栈就好了。
#include
#include
#include
using namespace std;
const int N=1000200;
int strl[N],strr[N],fl,fr;
int sum[N],f[N];
//对顶栈的移位操作
void push_stack(int x){
strl[++fl] = x;
sum[fl]=sum[fl-1]+x;
//求最大前缀和
f[fl]=max(f[fl-1],sum[fl]);
}
int main(){
int n;
cin>>n;
//f[0]一定要置为int类型的最小值,这样在找最小前缀和的时候如果是负数的情况才不会出错.否则,因为默认f[0]=0,会输出0
f[0]=INT_MIN;
while(n--){
char s[2];
scanf("%s",s);
int x;
if(*s=='I'){
cin>>x;
push_stack(x);
}
else if(*s=='D'){
if(fl>0) fl--;
}
else if(*s=='L'){
if(fl>0) strr[++fr]=strl[fl--];
}
else if(*s=='R'){
if(fr>0) push_stack(strr[fr]),fr--;
}
else{
cin>>x;
cout<<f[x]<<endl;
}
}
return 0;
}
原题链接:
https://www.acwing.com/problem/content/131/
题目描述
这里有 n 列火车将要进站再出站,但是,每列火车只有 1 节,那就是车头。
这 n 列火车按 1 到 n 的顺序从东方左转进站,这个车站是南北方向的,它虽然无限长,只可惜是一个死胡同,而且站台只有一条股道,火车只能倒着从西方出去,而且每列火车必须进站,先进后出。
也就是说这个火车站其实就相当于一个栈,每次可以让右侧头火车进栈,或者让栈顶火车出站。
车站示意如图:
出站<—— <——进站
|车|
|站|
|__|
现在请你按《字典序》输出前 20 种可能的出栈方案。
输入格式
输入一个整数 n,代表火车数量。
输出格式
按照《字典序》输出前 20 种答案,每行一种,不要空格。
数据范围
1≤n≤20
输入样例:
3
输出样例:
123
132
213
231
321
输出 1 、 2 、 . . . . . . 、 n {1、2、......、n} 1、2、......、n的按字典序前20个出栈情况
模拟栈的出栈情况,对于每一次栈有两个选择,要么进栈,要么出栈。
由此,我们可以知道我们需要三个变量:可以用stack3来表示入栈的数值,每入一次栈就加一;用stack2表示栈中的情况,当栈不为空的时候才可以出栈;stack1来表示出栈情况
我们可以通过递归来实现整个过程:
1.当stack1的长度为n时,那么达到边界
2.当stack2不为空的时候出栈
3.入栈
#include
#include
#include
using namespace std;
int n;
int cnt = 20,stack3;
vector<int> stack1;
stack<int> stack2;
void dfs(){
if(!cnt) return ;
//边界条件
if(stack1.size()==n){
cnt--;
for(int i=0;i<n;i++) cout<<stack1[i];
cout<<endl;
return ;
}
//出栈
if(!stack2.empty()){
stack1.push_back(stack2.top());
stack2.pop();
dfs();
stack2.push(stack1.back());
stack1.pop_back();
}
//入栈
if(stack3<n){
stack3++;
stack2.push(stack3);
dfs();
stack2.pop();
stack3--;
}
return;
}
int main(){
cin>>n;
dfs();
return 0;
}
原题链接:
https://www.acwing.com/problem/content/133/
题目描述
直方图是由在公共基线处对齐的一系列矩形组成的多边形。
矩形具有相等的宽度,但可以具有不同的高度。
例如,图例左侧显示了由高度为 2,1,4,5,1,3,3 的矩形组成的直方图,矩形的宽度都为 1:
通常,直方图用于表示离散分布,例如,文本中字符的频率。
现在,请你计算在公共基线处对齐的直方图中最大矩形的面积。
图例右图显示了所描绘直方图的最大对齐矩形。
输入格式
输入包含几个测试用例。
每个测试用例占据一行,用以描述一个直方图,并以整数 n 开始,表示组成直方图的矩形数目。
然后跟随 n 个整数 h1,…,hn。
这些数字以从左到右的顺序表示直方图的各个矩形的高度。
每个矩形的宽度为 1。
同行数字用空格隔开。
当输入用例为 n=0 时,结束输入,且该用例不用考虑。
输出格式
对于每一个测试用例,输出一个整数,代表指定直方图中最大矩形的区域面积。
每个数据占一行。
请注意,此矩形必须在公共基线处对齐。
数据范围
1≤n≤100000,
0≤hi≤1000000000
输入样例:
7 2 1 4 5 1 3 3
4 1000 1000 1000 1000
0
输出样例:
8
4000
对于第i个直方体,当它在扩展时,遇到左边第一个比它小的直方体l[i]停止,同理遇到右边第一个比它小的直方体r[i]则停止。l[i]、r[i]存的是左右第一个最小直方体的下标。那么可以得到它的面积就是(l[i]-1)-(r[i]-1)+1=l[i]-r[i]-1
那么问题就是,如何求左右两边最小的直方体。
这其实是数据结构中一个单调栈的问题。
关于单调栈学习记录的博客:https://blog.csdn.net/jigsaw_zyx/article/details/119825407
那么只要我们分别求出左右两边的单调栈就可以了
#include
#include
#include
#include
using namespace std;
int n;
const int N=100200;
//l[N],r[N],q[N]存的都是下标
int h[N],l[N],r[N],q[N];
typedef long long LL;
int main(){
while(~scanf("%d",&n)){
if(n==0) break;
for(int i=1;i<=n;i++) scanf("%d",&h[i]);
//这里一定要置为-1
h[0]=h[n+1]=-1;
//左边的第一个比它小的数
int t=0;
q[0]=0;
for(int i=1;i<=n;i++){
while(h[i]<=h[q[t]]) t--;
l[i]=q[t];
q[++t]=i;
}
//右边的第一个比它小的数
t=0;
q[0]=n+1;
for(int i=n;i;i--){
while(h[i]<=h[q[t]]) t--;
r[i]=q[t];
q[++t]=i;
}
LL res=0;
for(int i=1;i<=n;i++) res = max(res,(LL)h[i]*(r[i]-l[i]-1));
printf("%lld\n",res);
}
return 0;
}