“栈”这个数据结构,想必大家都很熟悉,它满足“后进先出”的性质。
squee_spoon做题的时候,有一种强迫症,即他总是先做最近的题目。例如,squee_spoon还没有解决问题a的时候,看到了问题b,那么他就会暂时放下a,先解决b。我们可以认为,squee_spoon做题总是严格满足栈的性质。然而squee_spoon太弱了,总会有一大堆未解决的问题,在问题积压数达到m的时候,他将难以维护他的“补题栈”,此时他不再接受新的问题(换句话说,他在任意时刻,最多存在m个未解决的问题)。
现在,squee_spoon拿到了一份题单,上面按顺序列有n道题的题号p1~pn,求squee_spoon按顺序读题号的情况下,可能的字典序最小的做题顺序。
题目要求字典序最小的做题顺序,所以很容易有个朴素的想法,首先让做的第一道题题号尽可能小,然后让第二道题题号尽可能小……这样得到的顺序肯定是最小的。
顺着这个思路往下想,有哪些题可以成为第一道呢,显然是前m题都可以,因为“补题栈”的大小是m,你就可以一直读到那一题,然后做掉,问题就转化为了“求数组p在区间[1,m]的最小值”。同理,哪些题可以成为当前能做的题呢,自然是往后的某个区间(区间长度取决于当前栈被占用了多少空间),以及当前的栈顶。
每次要做的题就是能做的题里面题号最小的那题,也就是相应区间的最小值和栈顶的值中更小的那个。于是这题的姿势就是快速求区间最小值辣!比如线段树(O(nlogn)),spare table(O(nlogn)),分块(O(n^1.5))都是可以过的。下面给一个线段树的解法:
#include <bits/stdc++.h>
#define N 100010
#define ll long long
using namespace std;
int a[N], seg_tree[N<<2];
void build(int root, int left, int right){
if (left == right){
scanf("%d", &a[left]);
seg_tree[root] = a[left];
return ;
}
int mid = (left+right)>>1;
build(root<<1, left, mid);
build(root<<1|1, mid+1, right);
seg_tree[root] = min(seg_tree[root<<1], seg_tree[root<<1|1]);
}
int query(int root, int left, int right, int L, int R){
if (left <= L && right >= R){
return seg_tree[root];
}
int mid = (L+R)>>1;
if (right <= mid) return query(root<<1, left, right, L, mid);
else if (left > mid) return query(root<<1|1, left, right, mid+1, R);
else return min(query(root<<1, left, right, L, mid), query(root<<1|1, left, right, mid+1, R));
}
stack<int> s;
bool first = true;
void Print(int x){
if (first){
first = false;
}else{
printf(" ");
}
printf("%d", x);
}
int main(){
#ifndef ONLINE_JUDGE
freopen("1.txt", "r", stdin);
#endif
int n, m, i, j;
cin >> n >> m;
build(1, 1, n);
for(i = 1; i <= n; i++){
int l = i, r = i+m-s.size()-1;
r = min(r, n);
int MIN = query(1, l, r, 1, n);
if (s.size() && s.top() < MIN){
Print(s.top());
s.pop();
i--;
}else{
Print(MIN);
while(a[i] != MIN){
s.push(a[i]);
i++;
}
}
}
while(!s.empty()){
Print(s.top());
s.pop();
}
puts("");
return 0;
}
以上内容转自http://blog.csdn.net/squee_spoon/article/details/50985698