《补题栈》

  “栈”这个数据结构,想必大家都很熟悉,它满足“后进先出”的性质。
  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;
}


你可能感兴趣的:(线段树)