您的位置:首页 > 其它

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

2017-05-20 21:57 381 查看

排序

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
=(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
.exist))
return 0;
if(l==r)
return a
.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
=tree[0];
available.push(n);
}

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

int mid=l+r>>1;
if(val<=mid)
build(tree
.l=apply(),l,mid,val);
else
build(tree
.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
.l].sum,mid=l+r>>1;
if(k<=siz)
return query(tree
.l,l,mid,k);
else
return query(tree
.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;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: