P6012 【模板】线段树分裂

(因为没有认证,所以这道题就由Froggy上传)
线段树分裂用到的地方确实并不多,luogu上以前也没有这道模板题,所以就出了一道,实在是想不出怎么出模板了,所以这道题可能可以用一些其他的算法水过去.

前置芝士

  1. 线段树: 本题中用到的是权值线段树(查询每个数在序列中出现的次数,序列中第k大的数等操作).
  2. 线段树合并: 为了增加一下码量才放上的.

线段树分裂

既然是模板题,这个自然才是重点.
以下这样一颗线段树:

需要将橙色线段覆盖的部分分裂出来,需要建一颗新的树,当一个节点所代表的区间与需要分裂的区间有交时需要开一个新的节点(绿色部分),原来的树中需要将那些整个被分裂的部分直接接到新的树下面,并且将于原来的树的边断开(红色部分),可以发现被断开的边最多只会有\(\log_2N\)条,所以最终每次分裂的时间复杂度就是\(\log_2N\)(证明方法与区间修改为\(N\log_2N\)相同).

代码

#include<bits/stdc++.h>
#define rap(i,first,last) for(int i=first;i<=last;++i)
//线段树标准define
#define Lson (tree[now].lson)
#define Rson (tree[now].rson)
#define Middle ((left+right)>>1)
#define Left Lson,left,Middle
#define Right Rson,Middle+1,right
#define Now nowleft,nowright
using namespace std;
const int maxN=6e5+7;//因为有分裂,需要大一点的空间
int N,M;
struct Tree//线段树中每一个节点
{
    int lson,rson;//动态开点,记录左右儿子
    long long sum;//区间和
}tree[maxN*4];
int cnt=0;
//一下是一个空间回收,可以将在线段树合并后没有用的点回收,更好地利用空间
int tot=0;//删掉的节点的个数
int rubbish[maxN*4];//用来放删掉的节点的编号
void Del(int &now)//删除一个节点
{
    tree[now].lson=tree[now].rson=tree[now].sum=0;//先清空
    rubbish[++tot]=now;//放入垃圾桶
    now=0;
}
int New()//得到一个新的节点
{
    if(tot)return rubbish[tot--];//垃圾桶不是空的就从垃圾桶上面拿一个
    return ++cnt;//垃圾桶是空的就拿一个全新的节点
}
//空间回收代码结束
int arr[maxN];//开始读入的数组
void PushUp(int now)//合并信息
{
    tree[now].sum=tree[Lson].sum+tree[Rson].sum;
}
void Build(int &now,int left=1,int right=N)//建树
{
    if(!now)now=New();//得到一个新节点
    if(left==right)
    {
        tree[now].sum=arr[left];//叶节点直接赋值
        return;
    }
    Build(Left);//建左子树
    Build(Right);//建右子树
    PushUp(now);//合并信息
}
void Merge(int &tree1,int &tree2,int left=1,int right=N)//线段树合并,将tree2合并到tree1上
{
    if(!tree1||!tree2)//如果当前这棵树只有其中一颗树有就可以直接赋值
    {
        tree1+=tree2;
        return;
    }
    if(left==right)//叶节点就直接合并
    {
        tree[tree1].sum+=tree[tree2].sum;
        Del(tree2);//删掉tree2
        return;
    }
    Merge(tree[tree1].lson,tree[tree2].lson,left,Middle);//继续合并
    Merge(tree[tree1].rson,tree[tree2].rson,Middle+1,right);
    Del(tree2);//删掉tree2
    PushUp(tree1);//合并信息
}
void Split(int &tree1,int &tree2,int nowleft,int nowright,int left=1,int right=N)//线段树分裂(将tree1中nowleft~nowright部分分裂到tree2中)
{
    if(right<nowleft||nowright<left)return;//没有被覆盖就直接返回
    if(!tree1)return;//如果tree1没有自然也没有用了
    if(nowleft<=left&&right<=nowright)//被完全覆盖时
    {
        tree2=tree1;
        tree1=0;//把边断开
        return;
    }
    if(!tree2)tree2=New();//得到一个新节点
    Split(tree[tree1].lson,tree[tree2].lson,Now,left,Middle);//左右子树继续分裂
    Split(tree[tree1].rson,tree[tree2].rson,Now,Middle+1,right);
    PushUp(tree1);//合并两数信息
    PushUp(tree2);
}
void UpData(int num,int add,int &now,int left=1,int right=N)//单点修改(不多讲)
{
    if(num<left||num>right)return;
    if(!now)now=New();
    if(left==right)
    {
        tree[now].sum+=add;
        return;
    }
    UpData(num,add,Left);
    UpData(num,add,Right);
    PushUp(now);
}
long long QuerySum(int nowleft,int nowright,int now,int left=1,int right=N)//查询区间和(不多讲)
{
    if(nowright<left||right<nowleft)return 0;
    if(!now)return 0;
    if(nowleft<=left&&right<=nowright)
    {
        return tree[now].sum;
    }
    return QuerySum(Now,Left)+QuerySum(Now,Right);
}
int QueryKth(long long k,int now,int left=1,int right=N)//查询区间第k大(不多讲)
{
    if(k<=0)return -1;
    if(left==right)return left;
    if(tree[Lson].sum>=k)return QueryKth(k,Left);
    return QueryKth(k-tree[Lson].sum,Right);
}
int root[maxN];//记录每一个序列的线段树的根节点
int cnttree=1;//序列的编号
int main()
{
    scanf("%d%d",&N,&M);
    rap(i,1,N)scanf("%d",&arr[i]);
    Build(root[1]);//以root[1]建树
    int check,p,t,x,y,k,q,kth;
    rap(i,1,M)
    {
        scanf("%d",&check);
        if(check==0)
        {
            scanf("%d%d%d",&p,&x,&y);
            Split(root[p],root[++cnttree]/*建一颗新树*/,x,y);//分裂
        }
        if(check==1)
        {
            scanf("%d%d",&p,&t);
            Merge(root[p],root[t]);//合并
        }
        if(check==2)
        {
            scanf("%d%d%d",&p,&x,&q);
            UpData(q,x,root[p]);//修改
        }
        if(check==3)
        {
            scanf("%d%d%d",&p,&x,&y);
            printf("%lld\n",QuerySum(x,y,root[p]));//区间和
        }
        if(check==4)
        {
            scanf("%d%d",&p,&k);
            if(QuerySum(1,N,root[p])<k)kth=-1;//区间和都没有k自然是不会有第k大了
            else
            kth=QueryKth(k,root[p]);
            printf("%d\n",kth);
        }
    }
}

原文地址:https://www.cnblogs.com/Sxy_Limit/p/12233746.html

时间: 2024-11-05 21:40:26

P6012 【模板】线段树分裂的相关文章

线段树分裂合并

线段树分裂合并 我先接触的是线段树合并所以先讲线段树合并. 首先,用来合并的线段树必须是动态开点的.线段树合并所做的事就是合并两棵动态开点线段树的信息,对于两棵动态开点线段树,可能会存在一些公共节点,我们所要做的就是合并这些节点的信息,然后把其他节点的信息继承.理清思路之后,剩下的事就是.设初始信息的个数是\(n\),值域是\(m\),对于每一个初始信息一次这样的操作是\(\log m\)的,而每个信息只会被合并一次,所以一般的线段树合并是\(O(n \log m)\)的.非常好理解,直接上模板

算法模板——线段树1(区间加法+区间求和)

实现功能——1:区间加法:2:区间求和 最基础最经典的线段树模板.由于这里面操作无顺序之分,所以不需要向下pushup,直接累积即可 1 var 2 i,j,k,l,m,n,a1,a2,a3,a4:longint; 3 a,b:array[0..100000] of longint; 4 function max(x,y:longint):longint;inline; 5 begin 6 if x>y then max:=x else max:=y; 7 end; 8 function min

算法模板——线段树5(区间开根+区间求和)

实现功能——1:区间开根:2:区间求和(此模板以BZOJ3038为例) 作为一个非常规的线段树操作,其tag也比较特殊呵呵哒 1 var 2 i,j,k,l,m,n:longint; 3 a,b:array[0..500000] of int64; 4 function max(x,y:longint):longint;inline; 5 begin 6 if x>y then max:=x else max:=y; 7 end; 8 function min(x,y:longint):long

【线段树】【P3372】模板-线段树

百度百科 Definition&Solution 线段树是一种log级别的树形结构,可以处理区间修改以及区间查询问题.期望情况下,复杂度为O(nlogn). 核心思想见百度百科,线段树即将每个线段分成左右两个线段做左右子树.一个线段没有子树,当且仅当线段表示的区间为[a,a]. 由于编号为k的节点的子节点为2k以及2k+1,线段树可以快速的递归左右叶节点. lazy标记:当进行区间修改的时候,如果一个区间整体全部被包含于要修改的区间,则可以将该区间的值修改后,将lazy标记打在区间上,不再递归左

CSU-ACM集训-模板-线段树进阶

A题 原CF 438D The Child and Sequence 题意 给一串数字,m次操作,1.区间查询:2.区间取模:3.单点修改 基本思路 考虑到模如果大于区间的最大值,则取模没有意义.若小于则向下查询并修改,考虑到一个数每次取模最多为原数的\(1/2\),则可认为修改次数不超过\(\log{2}{n}\) 时间复杂度为\(O(n\log{2}{n}\log{2}{n})\) #include<bits/stdc++.h> #define FOR(i,a,b) for(int i=a

模板 - 线段树

线段树还需要模板的菜鸡 #include<bits/stdc++.h> using namespace std; typedef long long ll; #define lt ls, l, m #define rt rs, m + 1, r #define ls (o<<1) #define rs (o<<1|1) const int MAXM = 100000 + 5; ll a[MAXM]; ll st[MAXM * 4], lazy[MAXM * 4]; in

算法模板——线段树4(区间加+区间乘+区间覆盖值+区间求和)

实现功能——1:区间加法 2:区间乘法 3:区间覆盖值 4:区间求和 这是个四种常见线段树功能的集合版哦...么么哒(其实只要协调好三种tag的关系并不算太难——前提是想明白了线段树的工作模式) 代码长度几经修改后也大为缩水 还有!!!——通过BZOJ1798反复的尝试,我的出来一个重要结论——尽量减少pushup操作的不必要使用次数,对于程序提速有明显的效果!!! 1 type vet=record 2 a0,a1:longint; 3 end; 4 var 5 i,j,k,l,m,n,a1,

算法模板——线段树7(骰子翻转问题)

实现功能:首先输入一个长度为N的序列,由1-4组成(1表示向前滚一下,2表示向后滚一下,3表示向左滚一下,4表示向右滚一下,骰子原始状态:上1前2左4右5后3下6),然后输入任意多个操作,输入“1 x y”表示将序列第x个数改成y,输入“2 x y”表示输出对于原始状态的骰子,按照从x到y的序列操作可以使骰子变成什么样子 原理:还是线段树,而且只需要点修改区间访问,不过这里面区间之间的合并不再是简单的累加了,而是——置换关系,通过置换关系的合并实现及时的维护即可 1 type 2 cube=ar

模板——线段树

一颗最简单的线段树orz...但是感觉还是拍得好麻烦... 只支持区间加和区间查询 #include<iostream> #include<cstdlib> #include<cstdio> #include<algorithm> using namespace std; int a[2000010],n,m; typedef long long ll; struct inlinetree{ int l,r; ll s,lazy; }lt[34000100]