“栈”这个数据结构,想必大家都很熟悉,它满足“后进先出”的性质。
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<cstdio> #include<algorithm> #include<cstring> #include<cmath> #include<iostream> #include<stack> using namespace std; #define ll long long int a[100010]; int seg_tree[100010<<2]; int tl[100010<<2]; int tr[100010<<2]; void build_tree(int rt,int l,int r){ tl[rt] = l; tr[rt] = r; if(l==r){ seg_tree[rt] = a[l]; return; } int mid = (l+r)>>1; build_tree(rt<<1,l,mid); build_tree((rt<<1)|1,mid+1,r); seg_tree[rt] = min(seg_tree[rt<<1],seg_tree[(rt<<1)|1]); } int query(int rt,int l,int r){ if(l==tl[rt] && r==tr[rt]){ return seg_tree[rt]; } int mid = (tl[rt] + tr[rt])>>1; if(r<=mid){ return query(rt<<1,l,r); }else{ if(l>mid){ return query((rt<<1)|1,l,r); }else{ int ql = query(rt<<1,l,mid); int qr = query((rt<<1)|1,mid+1,r); return min(ql,qr); } } } stack<int> sta; bool first = 1; void Print(int x){ if(first){ first=0; }else{ printf(" "); } printf("%d",x); } int main(){ int n,m; cin>>n>>m; for(int i=1;i<=n;i++){ scanf("%d",&a[i]); } build_tree(1,1,n); for(int i=1;i<=n;i++){ int rangeL=i; int rangeR=i+(m-sta.size()-1); rangeR=min(rangeR,n); int MIN = query(1,rangeL,rangeR); if(sta.size() && sta.top() < MIN){ Print(sta.top()); sta.pop(); i--; }else{ Print(MIN); while(a[i]!=MIN){ sta.push(a[i]); i++; } } } while(sta.size()){ Print(sta.top()); sta.pop(); } printf("\n"); return 0; }