线段树分裂与合并 ----- P2824 [HEOI2016/TJOI2016]排序 [线段树分裂合并 OR 01序列排序+二分线段树]

题目链接


题目大意:

对一个序列,每次按照升序或者降序排序序列某一段,问你最后的序列是什么?


解法1:二分+线段树

  1. 首先我们知道对一个01序列进行排序是很快的!我们只要知道里面有多少个1和多少个0,那么我们就可以用线段树区间修改每次排序才 O ( l o g n ) O(logn) O(logn)完美!
  2. 但是怎么融入到这道题里面呢?我们先二分出一个值 m i d mid mid,把比 m i d ≤ n u m mid\leq num midnum的值全部设置成1,其他全是0,然后排序一遍,如果排完序后那么 q q q位置是1的话,那么它排序之后就肯定大于或者等于当前的 m i d mid mid,那么我们就可以确定二分的方向了!!
  3. 时间复杂度 O ( m l o g 2 n ) O(mlog^2n) O(mlog2n)

AC代码:

#include <bits/stdc++.h>
#define mid ((l + r) >> 1)
#define Lson rt << 1, l , mid
#define Rson rt << 1|1, mid + 1, r
#define ms(a,al) memset(a,al,sizeof(a))
#define log2(a) log(a)/log(2)
#define lowbit(x) ((-x) & x)
#define IOS std::ios::sync_with_stdio(0); cin.tie(0); cout.tie(0)
#define INF 0x3f3f3f3f
#define LLF 0x3f3f3f3f3f3f3f3f
#define f first
#define s second
#define endl '\n'
using namespace std;
const int N = 2e6 + 10, mod = 1e9 + 9;
const int maxn = 500010;
const long double eps = 1e-5;
const int EPS = 500 * 500;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int,int> PII;
typedef pair<ll,ll> PLL;
typedef pair<double,double> PDD;
template<typename T> void read(T &x) {
   x = 0;char ch = getchar();ll f = 1;
   while(!isdigit(ch)){if(ch == '-')f*=-1;ch=getchar();}
   while(isdigit(ch)){x = x*10+ch-48;ch=getchar();}x*=f;
}
template<typename T, typename... Args> void read(T &first, Args& ... args) {
   read(first);
   read(args...);
}
int n, m;
int arr[maxn];
int q;
struct Op {
    int op, l, r;
}op[maxn];
struct Segtree { 
    int lazy;
    int val;
}sgt[maxn];
void pushup(int rt) {
    sgt[rt].val = sgt[rt<<1].val+sgt[rt<<1|1].val;
}

void pushdown(int rt, int l, int r) {
    if(~sgt[rt].lazy) {
        sgt[rt<<1].lazy = sgt[rt<<1|1].lazy = sgt[rt].lazy;
        sgt[rt<<1].val = sgt[rt].lazy * ((mid-l+1));
        sgt[rt<<1|1].val = sgt[rt].lazy * ((r-mid));
        sgt[rt].lazy = -1;
    }
}

void build(int rt, int l, int r, int tag) {
   sgt[rt].lazy = -1;
   if(l == r) {
       sgt[rt].val = (arr[l] >= tag);
       return;
   }
   build(Lson,tag);
   build(Rson,tag);
   pushup(rt);
}

int ask(int rt, int l, int r, int posl, int posr) {
    if(posl <= l && posr >= r) return sgt[rt].val;
    pushdown(rt,l,r);
    int sum = 0;
    if(posl <= mid) sum += ask(Lson,posl,posr);
    if(posr > mid) sum += ask(Rson,posl,posr);
    return sum;
} 

void update(int rt, int l, int r, int posl, int posr, int val) {
    if(posl > posr) return;
    if(posl <= l && posr >= r) {
        sgt[rt].val = (r - l + 1) * val;
        sgt[rt].lazy = val;
        return;
    }
    pushdown(rt,l,r);
    if(posl <= mid) update(Lson,posl,posr,val);
    if(posr > mid)  update(Rson,posl,posr,val);
    pushup(rt);
}

bool check(int Mid) {
    build(1,1,n,Mid);
    for(int i = 1; i <= m; ++ i) {
        int o = op[i].op;
        int l = op[i].l;
        int r = op[i].r;
        int sum1 = ask(1,1,n,l,r);
        int sum0 = r - l + 1 - sum1;
        //o=0就是升序0再前面,1再后面反之亦然!
        if(!o) update(1,1,n,l,l+sum0-1,0), update(1,1,n,l+sum0,r,1);
        else {
            update(1,1,n,l,l+sum1-1,1);
            update(1,1,n,l+sum1,r,0);
        }
    }
    return ask(1,1,n,q,q);//查询q位置是0是1
}

int main() {
    IOS;
    cin >> n >> m;
    for(int i = 1; i <= n; ++ i) cin >> arr[i];
    for(int i = 1; i <= m; ++ i) 
       cin >> op[i].op >> op[i].l >> op[i].r;
    cin >> q;
    int l = 1, r = n;
    while(l < r) {
        int Mid = (l+r+1)>>1;
        if(check(Mid)) l = Mid;
        else r = Mid - 1;
    }
    cout << r;
    return 0;
}
/*
6 2
1 6 2 5 3 4
1 3 6
0 2 4
3
*/

解法2:线段树合并与分裂

  1. 首先我们把每个数都看成自己的一个序列,对每个数都开一个权值线段树。假如我们要把 [ 1 , 4 ] [1,4] [1,4]区间进行排列那么我们就把 [ 1 , 4 ] [1,4] [1,4]里面的权值线段树全部都合并到1那么面去!
  2. 但是假如下次我们要对 [ 3 , 6 ] [3,6] [3,6]排序怎么办,那么我们知道 [ 3 , 4 ] [3,4] [3,4]的那一部分的数是再 r o o t [ 1 ] root[1] root[1]的权值线段树里面,那么我们要对 r o o t [ 1 ] root[1] root[1]控制的权值线段树进行分裂!!裂成 [ 1 , 2 ] 和 [ 3 , 4 ] [1,2]和[3,4] [1,2][3,4]拿分裂出来的线段再进行合并,但是每个区间升序或者是降序的,那么我们就要用一个数组记录一下这些区间的次序!!
  3. 根据上面的细节我们知道我们每段有序的区间实际上都说存储在这个区间的左端点的 r o o t [ l ] root[l] root[l]里面的,那么我们可以用一个 s e t set set去维护每个有序区间的左端点就可以了,因为你的区间可能被并到了多颗树里面,那你要先查找到 ( l o w e r _ b o u n d ) 肯 定 合 并 在 第 一 个 比 它 小 的 序 号 的 权 值 线 段 树 里 面 ! ! (lower\_bound)肯定合并在第一个比它小的序号的权值线段树里面!! lower_bound线!分裂然后再合并删除中间的区间!!
  4. 对于每个数最多合并一遍是 O ( n l o g n ) O(nlogn) O(nlogn)的,但是到最后每次就分裂两次合并两个区间,实际复杂度是 O ( m l o g n + n l o g n ) O(mlogn+nlogn) O(mlogn+nlogn) O ( ( n + m ) l o g n ) O((n+m)logn) O((n+m)logn)

AC代码

#include <bits/stdc++.h>
#define mid ((l + r) >> 1)
#define Lson rt << 1, l , mid
#define Rson rt << 1|1, mid + 1, r
#define ms(a,al) memset(a,al,sizeof(a))
#define log2(a) log(a)/log(2)
#define lowbit(x) ((-x) & x)
#define IOS std::ios::sync_with_stdio(0); cin.tie(0); cout.tie(0)
#define INF 0x3f3f3f3f
#define LLF 0x3f3f3f3f3f3f3f3f
#define f first
#define s second
#define endl '\n'
using namespace std;
const int N = 2e6 + 10, mod = 1e9 + 9;
const int maxn = 500010;
const long double eps = 1e-5;
const int EPS = 500 * 500;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int,int> PII;
typedef set<int>::iterator IT;
typedef pair<ll,ll> PLL;
typedef pair<double,double> PDD;
template<typename T> void read(T &x) {
   x = 0;char ch = getchar();ll f = 1;
   while(!isdigit(ch)){if(ch == '-')f*=-1;ch=getchar();}
   while(isdigit(ch)){x = x*10+ch-48;ch=getchar();}x*=f;
}
template<typename T, typename... Args> void read(T &first, Args& ... args) {
   read(first);
   read(args...);
}
int n, m, cnt, arr[maxn];
int root[maxn], tag[maxn];
set<int> seg;
struct node {
    int lson, rson;
    int val;
}sgt[maxn * 40];

inline void debug(int rt, int l, int r, int flag) {
    if(l == r) {
      cout << l << " ";
      return;
    }
    if(flag) {
        if(sgt[sgt[rt].rson].val) debug(sgt[rt].rson,mid+1,r,flag);
        if(sgt[sgt[rt].lson].val) debug(sgt[rt].lson,l,mid,flag);
    } else {
        if(sgt[sgt[rt].lson].val) debug(sgt[rt].lson,l,mid,flag);
        if(sgt[sgt[rt].rson].val) debug(sgt[rt].rson,mid+1,r,flag);
    }
}

inline void pushup(int rt) {
    sgt[rt].val = sgt[sgt[rt].lson].val + sgt[sgt[rt].rson].val;
    return;
}

void insert(int &rt, int l, int r, int val) {
    rt = ++ cnt;
    sgt[rt].val = 1;
    if(l == r) return;
    if(val <= mid) insert(sgt[rt].lson,l,mid,val);
    else insert(sgt[rt].rson,mid+1,r,val);
}

void split(int &x, int &y, int k, int o) {//分裂前k大或者后k大的区间
   if(!k || !y) return;
   x = ++ cnt;
   
   if(k == sgt[y].val) {
       sgt[x] = sgt[y];
       y = 0;
       return;
   }

   if(o) {//o大于0是降序排序的取权值线段树里面前K个数
       if(k <= sgt[sgt[y].lson].val) split(sgt[x].lson,sgt[y].lson,k,o);
       else split(sgt[x].rson,sgt[y].rson,k-sgt[sgt[y].lson].val,o), sgt[x].lson = sgt[y].lson, sgt[y].lson = 0;
   } else {// o小于0就是去权值线段树里面后面K大
       if(k <= sgt[sgt[y].rson].val) split(sgt[x].rson,sgt[y].rson,k,o);
       else split(sgt[x].lson,sgt[y].lson,k-sgt[sgt[y].rson].val,o), sgt[x].rson = sgt[y].rson, sgt[y].rson = 0;
   }
   pushup(x);
   pushup(y);
}

void meg(int &x, int y) {
    if(!(x&&y)) {x |= y;return;}
    else {
      sgt[x].val += sgt[y].val;
      meg(sgt[x].lson,sgt[y].lson);
      meg(sgt[x].rson,sgt[y].rson);
    }
}

inline IT getseg(int p) {
   IT poi = seg.lower_bound(p);//找第一个大于等于p的数
   if(*poi == p) return poi;//如果p不存在,那么就说明p位置被seg里面的第一个比它小的数给合并了
   -- poi;
   //因为每次都说维护左端,那么分裂区间就是右端的,那么这里就是要减去区间差
   split(root[p],root[*poi],sgt[root[*poi]].val-p+(*poi),tag[*poi]);
   tag[p] = tag[*poi];
   return seg.insert(p).first;
}

int ask(int rt, int l, int r) {
    if(l == r) return l;
    if(sgt[sgt[rt].lson].val) ask(sgt[rt].lson,l,mid);
    else ask(sgt[rt].rson,mid+1,r);
}




int main() { 
    //IOS;
    cin >> n >> m;
    seg.insert(n+1);
    for(int i = 1; i <= n; ++ i) cin >> arr[i];
    for(int i = 1; i <= n; ++ i)
       insert(root[i],0,n,arr[i]), seg.insert(i);
    while(m --) {
        int op, l, r;
        cin >> op >> l >> r;
        IT lp = getseg(l), rp = getseg(r+1);
        for(IT i = ++ lp; i != rp; i ++) meg(root[l],root[*i]);
        tag[l] = op;
        seg.erase(lp,rp);
    }
    int q;
    cin >> q;
    getseg(q),getseg(q+1);
    cout << ask(root[q],0,n);
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值