跳转至

线段树分裂

线段树分裂

通常是动态开点的权值线段树

sum 表示权值的个数

将线段树x从 k 处斩断,前 k 小的归 x ,后面的归 y

  • 若x为空,则返回

  • 对 y 动态开点

  • 递归分裂左子树,把x的右子树给 y, x 的右子树= 0

  • 递归分裂右子树,注意 k要减去s

  • 更新 y,x 的权值个数

步骤和平衡树里的getk很像

void split(int x,int &y,int k){
    //分裂线段树,前k大的归x,否则归y
    if(!x)return ;
    y=++tot;//动态开点
    int s=sum[ls[x]];
    if(k<=s)split(ls[x],ls[y].k)swap(rs[x],rs[y]);
    else split(rs[x],ry[x].k-s);
    sum[y]=sum[x]-k;
    sum[x]=k;
}

题目

给出一个可重集 \(a\)(编号为 \(1\)),它支持以下操作:

0 p x y:将可重集 \(p\) 中大于等于 \(x\) 且小于等于 \(y\) 的值移动到一个新的可重集中(新可重集编号为从 \(2\) 开始的正整数,是上一次产生的新可重集的编号+1)。

1 p t:将可重集 \(t\) 中的数放入可重集 \(p\),且清空可重集 \(t\)(数据保证在此后的操作中不会出现可重集 \(t\))。

2 p x q:在 \(p\) 这个可重集中加入 \(x\) 个数字 \(q\)

3 p x y:查询可重集 \(p\) 中大于等于 \(x\) 且小于等于 \(y\) 的值的个数。

4 p k:查询在 \(p\) 这个可重集中第 \(k\) 小的数,不存在时输出 -1

对于 \(30\%\) 的数据,\(1\leq n \leq {10}^3\)\(1 \le m \le {10}^3\); 对于 \(100\%\) 的数据,\(1 \le n \le 2 \times {10}^5\)\(1 \le x, y, q \le m \le 2 \times {10}^5\)。保证数据合法。

不开 long long 见祖宗!!

思路

很明显维护一个权值线段树。操作0就是分裂线段树,操作1就是合并线段树。操作4可以类比平衡树的操作。


#include<bits/stdc++.h>
using namespace std;
#define int long long
#define pii pair<int,int>
#define ps second
#define pf first


#define rd read()
int read()
{
    int xx=0,ff=1;
    char c=getchar();
    while(c<'0'||c>'9') {if(c=='-') ff=-1;c=getchar();}
    while(c>='0'&&c<='9') xx=xx*10+(c-'0'),c=getchar();
    return xx*ff;
}
void write(int out)
{
    if(out<0) putchar('-'),out=-out;
    if(out>9) write(out/10);
    putchar(out%10+'0');
}


const int N=3e5+5;
const int M=5e5+5;
const int INF=1e9+5;
const int base=23;
const int MOD=19260817;

int n,m,tot,cnt,seq=1,bac[N<<5];
int s[N<<5][2],rt[N],tr[N<<5];

namespace SGT{

    int newnode () {
        if(cnt)return bac[cnt--];//内存回收
        return ++tot;
    }
    void del (int p) {
        bac[++cnt]=p,s[p][0]=s[p][1]=tr[p]=0;
        return;
    }
    void modify (int &p,int l,int r,int pos,int v) {
        if (!p) {p=newnode();}
        tr[p]+=v;
        if (l==r) {return;}
        int mid=(l+r)>>1;
        if (pos<=mid) {modify(s[p][0],l,mid,pos,v);}
        else {modify(s[p][1],mid+1,r,pos,v);}
        return;
    }
    int query (int p,int l,int r,int xl,int xr) {
        if (xr<l||r<xl) {return 0;}
        if (xl<=l&&r<=xr) {return tr[p];}
        int mid=(l+r)>>1;
        return query(s[p][0],l,mid,xl,xr)+query(s[p][1],mid+1,r,xl,xr);
    }
    int kth (int p,int l,int r,int k) {
        if (l==r) {return l;}
        int mid=(l+r)>>1;
        if (tr[s[p][0]]>=k) {return kth(s[p][0],l,mid,k);}
        else {return kth(s[p][1],mid+1,r,k-tr[s[p][0]]);}
    }
    int merge (int x,int y) {
        if (!x||!y) {return x+y;}
        tr[x]+=tr[y];
        s[x][0]=merge(s[x][0],s[y][0]);
        s[x][1]=merge(s[x][1],s[y][1]);
        del(y); 
        return x;
    }
    void split (int x,int &y,int k) {
        if (x==0) {return;}
        y=newnode();
        int v=tr[s[x][0]];
        if (k>v) {split(s[x][1],s[y][1],k-v);}
        else {swap(s[x][1],s[y][1]);}
        if (k<v) {split(s[x][0],s[y][0],k);}
        tr[y]=tr[x]-k;
        tr[x]=k;
        return;
    }
}
using namespace SGT;

signed main () {
    n=rd,m=rd;

    for (int i=1;i<=n;i++) {
        int x=rd;
        modify(rt[1],1,n,i,x);
    }
    for (int i=1;i<=m;i++) {
        int op=rd;
        int x,y,z;
        if (op==0) {
            x=rd,y=rd,z=rd;
            int k1=query(rt[x],1,n,1,z),k2=query(rt[x],1,n,y,z);
            int tmp=0;
            split(rt[x],rt[++seq],k1-k2);
            split(rt[seq],tmp,k2);
            rt[x]=merge(rt[x],tmp);
        } else if (op==1) {
            x=rd,y=rd;
            rt[x]=merge(rt[x],rt[y]);
        } else if (op==2) {
            x=rd,y=rd,z=rd;
            modify(rt[x],1,n,z,y);
        } else if (op==3) {
            x=rd,y=rd,z=rd;
            printf("%lld\n",query(rt[x],1,n,y,z));
        } else if (op==4) {
            x=rd,y=rd;
            if (tr[rt[x]]<y) {printf("-1\n");continue;}
            printf("%lld\n",kth(rt[x],1,n,y));
        }
    }
    return 0;
}

扩展练习

www.luogu.com.cn

www.luogu.com.cn

www.luogu.com.cn

www.luogu.com.cn