线段树-Pudding Monster CF526F-单调栈

Pudding Monster

题目连接:https://www.luogu.org/problem/show?pid=CF526F

问题提出

  • 给长度为 n n n的排列 A A A.
  • 问有多少 ( l , r ) (l,r) (l,r),使得将 A l , A l + 1 , . . . , A r A_l,A_{l+1},...,A_r Al,Al+1,...,Ar排序之后是连续的一段数.
  • n ≤ 1 0 5 n \le 10^5 n105

问题解决

判断一段区间是否能连续,只要判断是否满足 m a x − m i n = r − l max-min=r-l maxmin=rl即可.

也即判断区间是否满足 m a x − m i n + l = r max-min+l = r maxmin+l=r.

先天有 m a x − m i n + l ≥ r max-min+l \ge r maxmin+lr

因此我们可以考虑枚举区间端点 r r r,然后使用线段树维护左边所有区间(每个区间用左端点唯一标识)的 l + m a x − m i n l+max-min l+maxmin的值.

我们还需要线段树满足求线段内最小值是谁,最小值有多少个的功能.

考虑到区间右端点向右移动的过程中,原有的区间再加上 r r r形成的新区间的 m a x max max不会变小, m i n min min值不会变大.

m a x max max变大也会是一批区间一起增大,增量也是相同的,因此可以转化为区间修改操作.

我们可以用 2 2 2个单调栈来维护当前的最大值序列,和最小值序列.

每次将 r r r点加入左边所有的区间,从而形成新的区间,根据单调栈保存的信息,确定出哪些区间的最大值或者是最小值需要被修改,然后进行区间修改.

询问时候,直接查询 [ 1 , r ] [1,r] [1,r]内最小值为 0 0 0的个数,算贡献.

代码

#include 
#include 
#include 
#define pr(x) std::cout << #x << ':' << x << std::endl
#define rep(i,a,b) for(int i = a;i <= b;++i)
#define clr(x) memset(x,0,sizeof(x))
#define setinf(x) memset(x,0x3f,sizeof(x))

const int N = 300007,inf = 1e9;
int n,m;
typedef std::pair<int,int> pii;
int a[N];

struct node{
    int val,cnt,tag,len;
    node(int val = 0,int cnt = 1,int tag = 0,int len = 1):val(val),cnt(cnt),tag(tag),len(len){}
}ns[N<<2];
//线段树维护的是区间最小值以及最小值的个数
inline void pushdown(int o) {
    if(ns[o].tag) {
        ns[o<<1].val += ns[o].tag;
        ns[o<<1|1].val += ns[o].tag;
        ns[o<<1].tag += ns[o].tag;
       	ns[o<<1|1].tag += ns[o].tag;
        ns[o].tag = 0;
    }
}

inline node maintain(node &lch,node &rch) {
    node res = node(std::min(lch.val,rch.val),0,0,lch.len+rch.len);
    if(lch.val == res.val) res.cnt += lch.cnt;
    if(rch.val == res.val) res.cnt += rch.cnt;
    return res;
}

inline void change(int o,int l,int r,int cl,int cr,int val) {
    if(cl <= l && r <= cr) {
        ns[o].tag += val;
        ns[o].val += val;
        return ;
    }	
    if(r < cl || cr < l) return;
    int mid = (l + r) >> 1;
    pushdown(o);
    change(o<<1,l,mid,cl,cr,val);
    change(o<<1|1,mid+1,r,cl,cr,val);
    ns[o] = maintain(ns[o<<1],ns[o<<1|1]);
}

inline node query(int o,int l,int r,int ql,int qr) {
    if(ql <= l && r <= qr) 
        return ns[o];
    if(r < ql || qr < l)
        return node(inf,1,0,0);
    int mid = (l + r) >> 1;
    pushdown(o);
    node lch = query(o<<1,l,mid,ql,qr);
    node rch = query(o<<1|1,mid+1,r,ql,qr);
    return maintain(lch,rch);
}

inline void build(int o,int l,int r) {
    if(l == r) {
        ns[o] = node(0,1,0,1);
        return ;
    }
    int mid = (l + r) >> 1;
    build(o<<1,l,mid);
    build(o<<1|1,mid+1,r);
    ns[o] = maintain(ns[o<<1],ns[o<<1|1]);
}

pii mx[N],mi[N];
int topx,topi;

int main() {
    std::cin >> n;
    build(1,1,n);
    rep(i,1,n) {
        int idx,y;
        std::cin >> idx >> y;
        a[idx] = y;
    }
    long long ans = 0;
    rep(i,1,n) {
        change(1,1,n,i,i,i);
        while(topx > 0 && mx[topx].first < a[i]) {
            change(1,1,n,mx[topx-1].second+1,mx[topx].second,a[i] - mx[topx].first);
            topx--;
        }
        mx[++topx] = (pii){a[i],i};
        while(topi > 0 && mi[topi].first > a[i]) {
            change(1,1,n,mi[topi-1].second+1,mi[topi].second,mi[topi].first - a[i]);
            topi--;
        }
        mi[++topi] = (pii){a[i],i};
        
        change(1,1,n,1,i,-i);
        node res = query(1,1,n,1,i);
        ans += res.cnt;
        change(1,1,n,1,i,i);
    }
    std::cout << ans << std::endl;
    return 0;
}

你可能感兴趣的:(ACM-ICPC训练题解,数据结构系列,线段树系列)