[BZOJ4552][TJOI2016&HEOI2016]排序-线段树合并

排序

Description

在2016年,佳媛姐姐喜欢上了数字序列。因而他经常研究关于序列的一些奇奇怪怪的问题,现在他在研究一个难题,需要你来帮助他。这个难题是这样子的:给出一个1到n的全排列,现在对这个全排列序列进行m次局部排序,排序分为两种:1:(0,l,r)表示将区间[l,r]的数字升序排序2:(1,l,r)表示将区间[l,r]的数字降序排序最后询问第q位置上的数字。

Input

输入数据的第一行为两个整数n和m。n表示序列的长度,m表示局部排序的次数。1 <= n, m <= 10^5第二行为n个整数,表示1到n的一个全排列。接下来输入m行,每一行有三个整数op, l, r, op为0代表升序排序,op为1代表降序排序, l, r 表示排序的区间。最后输入一个整数q,q表示排序完之后询问的位置, 1 <= q <= n。1 <= n <= 10^5,1 <= m <= 10^5

Output

输出数据仅有一行,一个整数,表示按照顺序将全部的部分排序结束后第q位置上的数字。

Sample Input

6 3
1 6 2 5 3 4
0 1 4
1 3 6
0 2 4
3

Sample Output

5


本来想学习一下线段树合并来着……
然后就找到了这题……
然后……怎么这么难啊啊啊啊啊啊!!!!!!
调了一晚上+一上午+半下午总算能过了……


思路:
初始对每一个单独的位置建一棵独立的线段树,都像主席树一样维护一个值域,每棵树初始仅有自身位置上的值这一个值加入值域。
为保证接下来能合并,咱必须保证树的结构相同,又由题面知这是一个全排列,所以每棵线段树的根节点所表示的值域范围均为[1,n]。
然后,每次排序就是合并能表示对应排序区间的数棵权值线段树~
但是,可能有时想要的左端点或右端点被包含在了某棵线段树所表示的区间内,则此时需要进行拆分对应的线段树,即split操作。
用一棵独立的非动态开点线段树来存储和查询在当前需查询区间的左端点的左边,最靠近它的一个根节点的左端点,每次在查询到它后从那个节点开始合并,直到凑出当前排序区间的一棵线段树,就可以结束操作了~
最后答案直接查询便可~

特殊方法思路:
merge(合并):递归两棵树的相同位置的两个节点,如果有一个为空则返回另一个,否则递归调用合并,返回值给其中一个节点,最后销毁另一个节点。
split(拆分):递归目标树和新树的相同位置的两个节点,按目标树当前节点的左儿子size大小判断递归拆分到左还是右,最后重新赋size的值。

注意:
1.对于升序和降序,只需要打个标记,在拆分时特判一下就好~
2.对于空间,可以采用代码中的方法用队列维护空白的节点……
3.细节巨多,因此代码中有英文注释~

那么,这个难题总算完成了!


#include<iostream>
#include<queue>
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<algorithm>

using namespace std;

const int M=2000010;
const int N=100010;

int n,m;

struct roots
{
    int l,r,type,nxt;
}root[M];

struct segment_tree
{
    struct data
    {
        bool exist;
        int rmax;
    }a[N<<2];

    inline void update(int x)
    {
        a[x]=(data){a[x<<1].exist||a[x<<1|1].exist,a[x<<1|1].rmax?a[x<<1|1].rmax:a[x<<1].rmax};
    }

    void ins(int n,int l,int r,int pos,int val)
    {
        if(l==r)
        {
            a[n]=(data){val?1:0,val};
            return;
        }

        int mid=l+r>>1;
        if(pos<=mid)
            ins(n<<1,l,mid,pos,val);
        else
            ins(n<<1|1,mid+1,r,pos,val);

        update(n);
    }

    int query(int n,int l,int r,int pos)
    {
        if(pos<l || (!a[n].exist))
            return 0;
        if(l==r)
            return a[n].rmax;

        int mid=l+r>>1,tmp;
        if(tmp=query(n<<1|1,mid+1,r,pos))
            return tmp;
        else if(mid<=pos)
            return a[n<<1].rmax;
        else
            return query(n<<1,l,mid,pos);
    }
}koishi;

struct node
{
    int l,r,sum;
}tree[M];

queue<int> available;

inline int apply()
{
    int ret=available.front();
    available.pop();
    return ret;
}

inline void free(int n)
{
    tree[n]=tree[0];
    available.push(n);
}

void build(int n,int l,int r,int val)
{
    tree[n].sum=1;
    if(l==r)
        return;

    int mid=l+r>>1;
    if(val<=mid)
        build(tree[n].l=apply(),l,mid,val);
    else
        build(tree[n].r=apply(),mid+1,r,val);
}

int merge(int n1,int n2)
{
    if(n1==0 || n2==0)
        return n1+n2;

    tree[n1].l=merge(tree[n1].l,tree[n2].l);
    tree[n1].r=merge(tree[n1].r,tree[n2].r);
    tree[n1].sum+=tree[n2].sum;

    free(n2);
    return n1;
}

void split(int n1,int n2,int k)
{
    int siz=tree[tree[n1].l].sum;

    if(k>siz)
        split(tree[n1].r,tree[n2].r=apply(),k-siz);
    else
        swap(tree[n2].r,tree[n1].r);

    if(k<siz)
        split(tree[n1].l,tree[n2].l=apply(),k);

    tree[n2].sum=tree[n1].sum-k;
    tree[n1].sum=k;
}

int query(int n,int l,int r,int k)
{
    if(l==r)
        return l;

    int siz=tree[tree[n].l].sum,mid=l+r>>1;
    if(k<=siz)
        return query(tree[n].l,l,mid,k);
    else
        return query(tree[n].r,mid+1,r,k-siz);
}

int main()
{
    for(int i=1;i<=M-10;i++)
        available.push(i);//save id

    scanf("%d%d",&n,&m);
    for(int i=1,last=M-5,val;i<=n;i++)
    {
        scanf("%d",&val);
        root[last].nxt=apply();//new root
        root[root[last].nxt]=(roots){i,i,0,0};//set current root's val:l=i,r=i

        koishi.ins(1,1,n,i,root[last].nxt);//add info of now
        build(root[last].nxt,1,n,val);//build tree for current root
        last=root[last].nxt;//reset last to current root
    }

    int now;
    while(m--)
    {
        int op,l,r;
        scanf("%d%d%d",&op,&l,&r);

        int ll=koishi.query(1,1,n,l),rr;//get the nearest root in l's left

        if(l==root[ll].l)//l already has a root,no need to split
        {
            rr=apply();//new right
            swap(tree[rr],tree[ll]);//move ll's info into rr
            swap(root[rr],root[ll]);//move ll's info into rr
            root[now=ll]=(roots){l,r,op,rr};//use ll buid a new root in this range
        }
        else
        {
            root[now=apply()]=(roots){l,r,op,rr=apply()};//apply now and nxt change to rr

            if(!root[ll].type)//if up
                split(ll,rr,l-root[ll].l);//cut [ll.l,l] from ll to rr
            else//down
                split(ll,rr,root[ll].r-l+1),//cut [ll.l,ll.l+ll.r-l+1] from ll to rr
                swap(tree[ll],tree[rr]);//swap ll's info to rr([ll.l+ll.r-l+2,ll.r] is the ans we need)

            root[rr]=root[ll];//copy root info
            root[rr].l=l;//reset l
            root[ll].r=l-1;//rest ll's r
            root[ll].nxt=now;//reset nxt=now
        }

        int nxt,last,tmp;
        for(nxt=rr;nxt && r>=root[nxt].r;)
        {
            merge(now,nxt);//from now to r merge all
            koishi.ins(1,1,n,root[nxt].l,0);//clear info of nxt

            last=nxt;//set nxt as last 
            nxt=root[nxt].nxt;//nxt rightmove
            root[last]=root[0];//clear nxt val;
        }

        root[now].nxt=nxt;//get the new range([l,r])'s nxt

        if(nxt!=0 && root[nxt].l<=r)//if nxt's l is in [l,r] range
        {
            koishi.ins(1,1,n,root[nxt].l,0);//clear info of nxt

            if(root[nxt].type)
                split(nxt,tmp=apply(),root[nxt].r-r);//cut [nxt.l,r] to a new tmp
            else
                split(nxt,tmp=apply(),r-root[nxt].l+1),//cut [nxt.l,r+1] to tmp
                swap(tree[nxt],tree[tmp]);

            merge(now,tmp);//merge now with tmp
            root[nxt].l=r+1;//update nxt.l
            koishi.ins(1,1,n,root[nxt].l,nxt);//reset info for nxt
        }

        koishi.ins(1,1,n,l,now);//add info for now
    }

    int pos;
    scanf("%d",&pos);
    now=koishi.query(1,1,n,pos);//query in which root
    if(root[now].type)
        printf("%d\n",query(now,1,n,root[now].r-pos+1));//query pos
    else
        printf("%d\n",query(now,1,n,pos-root[now].l+1));//query pos

    return 0;
}
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值