线段树合并与分裂

http://blog.csdn.net/zawedx/article/details/51818475

由于上面这篇文章讲的很清楚了,不打算再讲一遍......骗访问量也要按基本法

利用这种动态开点的值域线段树可以解决一堆有序集合进行合并/分裂/查询k小的问题,最好用的就是在排序问题中。

例1 bzoj4552

m次排序,每次对一个区间升序或降序排序,最后询问一个位置的值。

有一种比较咸鱼的做法是二分答案然后转化为01序列来做,这里就不说了= =

我们可以把排序好的一坨当做一个有序集合,用一个set维护一下这些集合的起止端点,然后排序的时候只要把这些集合全部并在一起就行了。

#include <iostream>
#include <stdio.h>
#include <math.h>
#include <string.h>
#include <time.h>
#include <stdlib.h>
#include <string>
#include <bitset>
#include <vector>
#include <set>
#include <map>
#include <queue>
#include <algorithm>
#include <sstream>
#include <stack>
#include <iomanip>
using namespace std;
#define pb push_back
#define mp make_pair
typedef pair<int,int> pii;
typedef long long ll;
typedef double ld;
typedef vector<int> vi;
#define fi first
#define se second
#define fe first
#define FO(x) {freopen(#x".in","r",stdin);freopen(#x".out","w",stdout);}
#define Edg int M=0,fst[SZ],vb[SZ],nxt[SZ];void ad_de(int a,int b){++M;nxt[M]=fst[a];fst[a]=M;vb[M]=b;}void adde(int a,int b){ad_de(a,b);ad_de(b,a);}
#define Edgc int M=0,fst[SZ],vb[SZ],nxt[SZ],vc[SZ];void ad_de(int a,int b,int c){++M;nxt[M]=fst[a];fst[a]=M;vb[M]=b;vc[M]=c;}void adde(int a,int b,int c){ad_de(a,b,c);ad_de(b,a,c);}
#define es(x,e) (int e=fst[x];e;e=nxt[e])
#define esb(x,e,b) (int e=fst[x],b=vb[e];e;e=nxt[e],b=vb[e])
#define VIZ {printf("digraph G{\n"); for(int i=1;i<=n;i++) for es(i,e) printf("%d->%d;\n",i,vb[e]); puts("}");}
#define VIZ2 {printf("graph G{\n"); for(int i=1;i<=n;i++) for es(i,e) if(vb[e]>=i)printf("%d--%d;\n",i,vb[e]); puts("}");}
#define SZ 666666
#define SG 9999999
//merge-split seg begin
int ss[SG],sn=0,ch[SG][2],s[SG];
#define er(x) ss[++sn]=x
inline int alc_()
{
    int x=ss[sn--];
    ch[x][0]=ch[x][1]=s[x]=0;
    return x;
}
#define alc alc_()
//make a seg with only node p
void build(int& x,int l,int r,int p)
{
    x=alc; s[x]=1;
    //cout<<"build"<<x<<","<<l<<","<<r<<‘,‘<<p<<"\n";
    if(l==r) return;
    int m=(l+r)>>1;
    if(p<=m) build(ch[x][0],l,m,p);
    else build(ch[x][1],m+1,r,p);
}
//merge t1&t2
int merge(int t1,int t2)
{
    if(t1&&t2);else return t1^t2;
    ch[t1][0]=merge(ch[t1][0],ch[t2][0]);
    ch[t1][1]=merge(ch[t1][1],ch[t2][1]);
    s[t1]+=s[t2]; er(t2); return t1;
}
//split t1 to t1&t2 so that s[t1]=k
void split(int t1,int&t2,int k)
{
    t2=alc;
    int ls=s[ch[t1][0]];
    if(k>ls) split(ch[t1][1],ch[t2][1],k-ls);
    else swap(ch[t1][1],ch[t2][1]);
    if(k<ls) split(ch[t1][0],ch[t2][0],k);
    s[t2]=s[t1]-k; s[t1]=k;
}
int ask(int x,int l,int r,int k)
{
    if(l==r) return l;
    int ls=s[ch[x][0]];
    int m=(l+r)>>1;
    if(k>ls) return ask(ch[x][1],m+1,r,k-ls);
    return ask(ch[x][0],l,m,k);
}
void ps(int x,int l,int r,int dep=0)
{
    if(!x) return;
    for(int i=1;i<=dep;i++) putchar(‘ ‘);
    cout<<x<<" "<<s[x]<<" "<<l<<","<<r<<" lc"<<ch[x][0]<<"rc"<<ch[x][1]<<"\n";
    ps(ch[x][0],l,(l+r)>>1,dep+1);
    ps(ch[x][1],(l+r)/2+1,r,dep+1);
}
//seg end
bool typ[SZ]; //0<  1>
int rots[SZ],rs[SZ]; //i pos store l=i
set<int> sol;
int n,q,a[SZ];
//split x so that rs[x]=s
void sp(int x,int s)
{
    //cout<<"split"<<x<<","<<s<<"\n";
    if(s>=rs[x]||s<x) return;
    if(!typ[x]) split(rots[x],rots[s+1],s-x+1);
    else
    {
        rots[s+1]=rots[x];
        split(rots[s+1],rots[x],rs[x]-s);
    }
    rs[s+1]=rs[x]; rs[x]=s;
    sol.insert(s+1); typ[s+1]=typ[x];
}
//it‘s your task to edit typ[a]!
void mg(int a,int b)
{
    //cout<<"mg"<<a<<","<<b<<"\n";
    if(a==b) return;
    sol.erase(b);
    rots[a]=merge(rots[a],rots[b]);
    rs[a]=rs[b];
    //cout<<a<<","<<rs[a]<<"\n";
}
int qpos(int x,int k)
{
    k-=x-1;
    if(!typ[x]) return ask(rots[x],1,n,k);
    else return ask(rots[x],1,n,rs[x]-x+2-k);
}
void dbg()
{
    cout<<sol.size()<<"\n";
    for(set<int>::iterator g=sol.begin();g!=sol.end();g++)
    {
        cout<<*g<<" "<<rs[*g]<<" "<<typ[*g]<<"\n";
        ps(rots[*g],1,n);
    }
}
#define ch ____ch
char ch,B[1<<15],*S=B,*T=B;
#define getc() (S==T&&(T=(S=B)+fread(B,1,1<<15,stdin),S==T)?0:*S++)
#define isd(c) (c>=‘0‘&&c<=‘9‘)
int aa,bb;int F(){
    while(ch=getc(),!isd(ch)&&ch!=‘-‘);ch==‘-‘?aa=bb=0:(aa=ch-‘0‘,bb=1);
    while(ch=getc(),isd(ch))aa=aa*10+ch-‘0‘;return bb?aa:-aa;
}
#define gi F()
int main()
{
    for(int i=SG-1;i>=1;i--) ss[++sn]=i;
    //scanf("%d%d",&n,&q);
    n=gi, q=gi;
    for(int i=1;i<=n;i++)
    {
        a[i]=gi;
        //scanf("%d",a+i);
        build(rots[i],1,n,a[i]);
        sol.insert(i); rs[i]=i;
    }
    static int tmp[SZ],tn=0;
    while(q--)
    {
        int o=gi,l=gi,r=gi;
        //scanf("%d%d%d",&o,&l,&r);
        {
        set<int>::iterator w=sol.upper_bound(l); sp(*(--w),l-1);
        w=sol.upper_bound(r); sp(*(--w),r);
        }
        set<int>::iterator p1=sol.lower_bound(l),
        p2=sol.upper_bound(r); --p2;
        int tg=*p1;
        if(p1!=p2)
        {
        for(set<int>::iterator i=++p1;;++i)
        {
            tmp[++tn]=*i;
            if(i==p2) break;
        }
        for(int i=1;i<=tn;i++) mg(tg,tmp[i]); tn=0;
        }
        typ[tg]=o;
    }
    int r=gi;
    set<int>::iterator w=sol.upper_bound(r);
    int x=*(--w); printf("%d\n",qpos(x,r));
}

例2 yzoj2753

问题可以被当做是把序列中的一段进行升序/降序排序或者交换两个元素。

同样可以咸鱼一点二分答案之后做,不过这也不是重点...

做法和上一题差不多。

#include <iostream>
#include <stdio.h>
#include <math.h>
#include <string.h>
#include <time.h>
#include <stdlib.h>
#include <string>
#include <bitset>
#include <vector>
#include <set>
#include <map>
#include <queue>
#include <algorithm>
#include <sstream>
#include <stack>
#include <iomanip>
using namespace std;
#define pb push_back
#define mp make_pair
typedef pair<int,int> pii;
typedef long long ll;
typedef double ld;
typedef vector<int> vi;
#define fi first
#define se second
#define fe first
#define FO(x) {freopen(#x".in","r",stdin);freopen(#x".out","w",stdout);}
#define Edg int M=0,fst[SZ],vb[SZ],nxt[SZ];void ad_de(int a,int b){++M;nxt[M]=fst[a];fst[a]=M;vb[M]=b;}void adde(int a,int b){ad_de(a,b);ad_de(b,a);}
#define Edgc int M=0,fst[SZ],vb[SZ],nxt[SZ],vc[SZ];void ad_de(int a,int b,int c){++M;nxt[M]=fst[a];fst[a]=M;vb[M]=b;vc[M]=c;}void adde(int a,int b,int c){ad_de(a,b,c);ad_de(b,a,c);}
#define es(x,e) (int e=fst[x];e;e=nxt[e])
#define esb(x,e,b) (int e=fst[x],b=vb[e];e;e=nxt[e],b=vb[e])
#define VIZ {printf("digraph G{\n"); for(int i=1;i<=n;i++) for es(i,e) printf("%d->%d;\n",i,vb[e]); puts("}");}
#define VIZ2 {printf("graph G{\n"); for(int i=1;i<=n;i++) for es(i,e) if(vb[e]>=i)printf("%d--%d;\n",i,vb[e]); puts("}");}
#define SZ 666666
#define SG 7777777
//merge-split seg begin
int ss[SG],sn=0,ch[SG][2],s[SG];
#define er(x) ss[++sn]=x
inline int alc_()
{
    int x=ss[sn--];
    ch[x][0]=ch[x][1]=0;//s[x]=0;
    return x;
}
#define alc alc_()
//make a seg with only node p
void build(int& x,int l,int r,int p)
{
    x=alc; s[x]=1;
    if(l==r) return;
    int m=(l+r)>>1;
    if(p<=m) build(ch[x][0],l,m,p);
    else build(ch[x][1],m+1,r,p);
}
//merge t1&t2
int merge(int t1,int t2)
{
    if(t1&&t2);else return t1^t2;
    ch[t1][0]=merge(ch[t1][0],ch[t2][0]);
    ch[t1][1]=merge(ch[t1][1],ch[t2][1]);
    s[t1]+=s[t2]; er(t2); return t1;
}
//split t1 to t1&t2 so that s[t1]=k
void split(int t1,int&t2,int k)
{
    t2=alc;
    if(ch[t1][0]||ch[t1][1])
    {
        int ls=s[ch[t1][0]];
        if(k>ls) split(ch[t1][1],ch[t2][1],k-ls);
        else swap(ch[t1][1],ch[t2][1]);
        if(k<ls) split(ch[t1][0],ch[t2][0],k);
    }
    s[t2]=s[t1]-k; s[t1]=k;
}
int ask(int x,int l,int r,int k)
{
    if(l==r) return l;
    int ls=s[ch[x][0]];
    int m=(l+r)>>1;
    if(k>ls) return ask(ch[x][1],m+1,r,k-ls);
    return ask(ch[x][0],l,m,k);
}
//seg end
typedef set<int> Set;
int R=1e9;
Set sol;
bool typ[SZ]; //0<  1>
int rots[SZ],rs[SZ]; //i pos store l=i
int n,q,a[SZ];
//split x so that rs[x]=s
void sp(int x,int s)
{
    if(s>=rs[x]||s<x) return;
    if(!typ[x]) split(rots[x],rots[s+1],s-x+1);
    else
    {
        rots[s+1]=rots[x];
        split(rots[s+1],rots[x],rs[x]-s);
    }
    rs[s+1]=rs[x]; rs[x]=s;
    sol.insert(s+1); typ[s+1]=typ[x];
}
//it‘s your task to edit typ[a]!
void mg(int a,int b)
{
    if(a==b) return;
    sol.erase(b);
    rots[a]=merge(rots[a],rots[b]);
    rs[a]=rs[b];
}
int qpos(int x,int k)
{
    k-=x-1;
    if(!typ[x]) return ask(rots[x],0,R,k);
    else return ask(rots[x],0,R,rs[x]-x+2-k);
}
#define fpos my_fpos
int fpos(int x)
{
    Set::iterator w=sol.upper_bound(x);
    return *(--w);
}
#define ch reader_ch
char ch,B[1<<15],*S=B,*T=B;
#define getc() (S==T&&(T=(S=B)+fread(B,1,1<<15,stdin),S==T)?0:*S++)
#define isd(c) (c>=‘0‘&&c<=‘9‘)
int aa,bb;int F(){
    while(ch=getc(),!isd(ch)&&ch!=‘-‘);ch==‘-‘?aa=bb=0:(aa=ch-‘0‘,bb=1);
    while(ch=getc(),isd(ch))aa=aa*10+ch-‘0‘;return bb?aa:-aa;
}
#define gi F()
void sort(int l,int r,int o)
{
    if(l==r) return;
    static int tmp[SZ],tn=0;
    sp(fpos(l),l-1); sp(fpos(r),r);
    Set::iterator p1=sol.lower_bound(l),
    p2=sol.upper_bound(r); --p2;
    int tg=*p1;
    if(p1!=p2)
    {
        for(Set::iterator i=++p1;;++i)
        {
            tmp[++tn]=*i;
            if(i==p2) break;
        }
        for(int i=1;i<=tn;i++) mg(tg,tmp[i]); tn=0;
    }
    typ[tg]=o;
}
void qwq()
{
    sol.clear();
    sn=R=0;
    for(int i=SG-1;i>=1;i--) ss[++sn]=i;
    n=gi, q=gi;
    for(int i=1;i<=n+n;i++) R=max(R,a[i]=gi);
    for(int i=1;i<=n+n;i++)
    {
        build(rots[i],0,R,a[i]);
        sol.insert(i); rs[i]=i;
    }
    while(q--)
    {
        int o=gi,a=gi,b=gi,c=gi,d=gi;
        if(o==1)
        {
            int p=a*n+b,q=a*n+c;
            sort(p,q,d);
        }
        else
        {
            int p=a*n+b,q=c*n+d;
            sp(fpos(p),p-1);
            sp(fpos(p),p);
            sp(fpos(q),q-1);
            sp(fpos(q),q);
            swap(rots[p],rots[q]);
        }
    }
    int x_=gi,y_=gi,x=x_*n+y_;
    printf("%d\n",qpos(fpos(x),x));
}
int main()
{
    int T=gi;
    while(T--) qwq();
}

于是好好的安利文就成了贴板子= =如果有比较有趣的题再更新吧

另外不是很懂splay和treap要怎么搞这个东西啊

时间: 2024-10-25 06:09:45

线段树合并与分裂的相关文章

JZOJ4605. 排序(线段树合并与分裂)

题目大意: 每次把一个区间升序或降序排序,最后问一个点是什么. 题解: 如果只是问一个点,这确乎是个经典题,二分一下答案然后线段树维护01排序. 从pty那里get到了可以用线段树的合并与分裂实时地维护整个序列. 考虑一次排序就把这个区间的数搞到一个线段树上,在根处标记是正的还是反的. 如果想搞到一棵树上就需要用到分裂与合并,根据势能分析,复杂度还是\(O(n~log~n)\). Code: #include<bits/stdc++.h> #define fo(i, x, y) for(int

【线段树合并】【P2824】 [HEOI2016/TJOI2016]排序

Description 给定一个长度为 \(n\) 的排列,有 \(m\) 次操作,每次选取一段局部进行升序或降序排序,问你一波操作后某个位置上的数字是几 Hint \(1~\leq~n,~m~\leq~10^5\) Solution 有两种做法,一种在线一种离线,这里把在线部分讲得更清楚点吧-- 考虑离线算法,我们二分该位置上的答案,将大于该数的元素置为 \(1\),小于该数的元素置为 \(0\),然后模拟所有的排序并检验.由于使用线段树对 \(0/1\) 序列多次局部排序可以做到 \(O(m

【BZOJ4399】魔法少女LJJ 线段树合并

[BZOJ4399]魔法少女LJJ Description 在森林中见过会动的树,在沙漠中见过会动的仙人掌过后,魔法少女LJJ已经觉得自己见过世界上的所有稀奇古怪的事情了LJJ感叹道“这里真是个迷人的绿色世界,空气清新.淡雅,到处散发着醉人的奶浆味:小猴在枝头悠来荡去,好不自在:各式各样的鲜花争相开放,各种树枝的枝头挂满沉甸甸的野果:鸟儿的歌声婉转动听,小河里飘着落下的花瓣真是人间仙境”SHY觉得LJJ还是太naive,一天,SHY带着自己心爱的图找到LJJ,对LJJ说:“既然你已经见识过动态树

【BZOJ2733】永无乡[splay启发式合并or线段树合并]

题目大意:给你一些点,修改是在在两个点之间连一条无向边,查询时求某个点能走到的点中重要度第k大的点.题目中给定的是每个节点的排名,所以实际上是求第k小:题目求的是编号,不是重要度的排名.我一开始差点被这坑了. 网址:http://www.lydsy.com/JudgeOnline/problem.php?id=2733 这道题似乎挺经典的(至少我看许多神犇很早就做了这道题).这道题有两种写法:并查集+(splay启发式合并or线段树合并).我写的是线段树合并,因为--splay不会打+懒得学.

BZOJ 4756 线段树合并(线段树)

思路: 1.最裸的线段树合并 2. 我们可以观察到子树求一个东西 那我们直接DFS序好了 入队的时候统计一下有多少比他大的 出的时候统计一下 减一下 搞定~ 线段树合并代码: //By SiriusRen #include <cstdio> #include <cstring> #include <algorithm> using namespace std; const int N=100050; int n,col[N],cpy[N],tree[N*100],lso

bzoj2733: [HNOI2012]永无乡(splay+启发式合并/线段树合并)

这题之前写过线段树合并,今天复习Splay的时候想起这题,打算写一次Splay+启发式合并. 好爽!!! 写了长长的代码(其实也不长),只凭着下午的一点记忆(没背板子...),调了好久好久,过了样例,submit,1A! 哇真的舒服 调试输出懒得删了QwQ #include<iostream> #include<cstdlib> #include<cstring> #include<cstdio> #include<queue> #include

[BZOJ3545] [ONTAK2010]Peaks(线段树合并 + 离散化)

传送门 由于困难值小于等于x这个很恶心,可以离线处理,将边权,和询问时的x排序. 每到一个询问的时候,将边权小于等于x的都合并起来再询问. .. 有重复元素的线段树合并的时间复杂度是nlog^2n #include <cstdio> #include <iostream> #include <algorithm> #define N 500001 int n, m, q, cnt, tot, size; int sum[N * 10], ls[N * 10], rs[N

[XJOI NOI2015模拟题13] C 白黑树 【线段树合并】

题目链接:XJOI - NOI2015-13 - C 题目分析 使用神奇的线段树合并在 O(nlogn) 的时间复杂度内解决这道题目. 对树上的每个点都建立一棵线段树,key是时间(即第几次操作),动态开点. 线段树的节点维护两个值,一个是这段时间内的 1 操作个数,另一个是这段时间内变化的黑色节点权值和. 在处理所有操作的时候,每棵线段树都是仅代表树上的一个点,因此线段树的每个节点维护的就是这段时间内以这个点为 a 的 1 操作个数和这段时间内这个点的黑色节点权值和(这个点 x 由黑变白就 -

[BZOJ 2212] [Poi2011] Tree Rotations 【线段树合并】

题目链接:BZOJ - 2212 题目分析 子树 x 内的逆序对个数为 :x 左子树内的逆序对个数 + x 右子树内的逆序对个数 + 跨越 x 左子树与右子树的逆序对. 左右子树内部的逆序对与是否交换左右子树无关,是否交换左右子树取决于交换后 “跨越 x 左子树与右子树的逆序对” 是否会减小. 因此我们要求出两种情况下的逆序对数,使用线段树合并,对每个节点建一棵线段树,然后合并的同时就求出两种情况下的逆序对. 代码 #include <iostream> #include <cstdli