首页 > 其他分享 >线段树合并

线段树合并

时间:2024-10-17 18:47:59浏览次数:2  
标签:int 线段 合并 sgt fa 节点

线段树合并,顾名思义,就是将两个线段树合并在一起(这里的线段树大多是动态开点权值线段树)

首先线段树合并就是把一大堆权值线段树合并起来的算法

实现流程就是,同时递归的遍历两个要合并的线段树 \(A,B\),有如下几种情况:
1.现在在合并 区间 \(2,4\) 对应的节点, \(A\) 有而 \(B\) 没有这个节点,那么我们直接返回 \(A\) 的这个节点
2.现在在合并区间 \(2,2\) 对应的节点 因为是叶子节点,所以我们直接将 这这两棵树的这个节点的值相加,返回
3.那不是叶子节点两棵树都有的节点呢?将这两颗树的这个节点的左右儿子分别合并即可

尽管复杂度看起来并不是非常科学,但是确是非常优秀的 \(O(nlogn)\)

主要的写法两种

int merge(int x,int y,int l,int r)//将 y 合并到 x 上(注意,这种合并后 y 不可以再与别的线段树合并)
{
    if(!x||!y)//有一边节点为空
    {
        return x+y;//返回另一边的
    }
    if(l==r)//是叶子节点
    {
        sgt[x].sum+=sgt[y].sum;//加起来
        return x;//返回
    }
    int mid=(l+r)>>1;
    sgt[x].ls=merge(sgt[x].ls,sgt[y].ls,l,mid);//左右儿子节点分别合并
    sgt[x].rs=merge(sgt[x].rs,sgt[y].rs,mid+1,r);
    update(x);//更新这个节点
    return x;
}

把 \(y\) 合并到 \(x\) 上

但是我们这样直接把 \(y\) 合并过来的话,因为 \(x\) 共用了 \(y\) 的节点,以后 \(y\) 与别的树合并时就可能影响到 \(x\)

所以这种写法中的 \(y\) 树在之后就不能与别的树合并

我们也可以像主席树那样,合并不在原来的树上而是新开节点,


缺点就是特别特别炸空间

总的代码:P4556 [Vani有约会] 雨天的尾巴-模板-线段树合并
#include <bits/stdc++.h>
using namespace std;
struct node //线段树
{
    int l, r;
    int ls, rs;

    int sum;
    int typ;
};
node sgt[1000000];
struct tree //树
{
    int dfn;
    vector<int> son;
    int deep;
};
int n,m;
tree tr[100005];
int root[100005], idx; //树根记录
int fa[100005][20];
int dfn;
int dfn_[100005];//DFN序
void build_t(int x, int f)//倍增
{
    tr[x].deep = tr[f].deep + 1;
    fa[x][0] = f;
    tr[x].dfn=++dfn;
    dfn_[dfn]=x;
    for (int i = 1; i <= 18; i++)
    {
        fa[x][i] = fa[fa[x][i - 1]][i - 1];
    }
    for (int to : tr[x].son)
    {
        if (to != f)
        {
            build_t(to, x);
        }
    }
}
int get_lca(int x, int y)
{
    if (tr[x].deep < tr[y].deep)
    {
        swap(x, y);
    }
    for (int i = 18; i >= 0; i--)
    {
        if (tr[fa[x][i]].deep >= tr[y].deep)
        {
            x = fa[x][i];
        }
    }
    if (x == y)
    {
        return y;
    }
    for (int i = 18; i >= 0; i--)
    {
        if (fa[x][i] != fa[y][i])
        {
            x = fa[x][i];
            y = fa[y][i];
        }
    }
    return fa[x][0];
}

// sgt启动!
void update(int p) //上传 权值线段树
{
    if (sgt[sgt[p].ls].sum >= sgt[sgt[p].rs].sum) //取左右子树中最大的sum
    {
        sgt[p].sum = sgt[sgt[p].ls].sum;
        sgt[p].typ = sgt[sgt[p].ls].typ;
    }
    else
    {
        sgt[p].sum = sgt[sgt[p].rs].sum;
        sgt[p].typ = sgt[sgt[p].rs].typ;
    }
}
void change(int &p,int l,int r,int kind_,int delt)//点修
{
    if(!p)
    {
        p=++idx;
    }
    if(l==r)
    {
        sgt[p].sum+=delt;
        sgt[p].typ=kind_;
        return;
    }
    int mid=(l+r)>>1;
    if(kind_<=mid)
    {
        change(sgt[p].ls,l,mid,kind_,delt);
    }
    else
    {
        change(sgt[p].rs,mid+1,r,kind_,delt);
    }
    update(p);
}
int merge(int x,int y,int l,int r)//线段树合并
{
    if(!x||!y)
    {
        return x+y;
    }
    if(l==r)
    {
        sgt[x].sum+=sgt[y].sum;
    }
    int mid=(l+r)>>1;
    sgt[x].ls=merge(sgt[x].ls,sgt[y].ls,l,mid);
    sgt[x].rs=merge(sgt[x].rs,sgt[y].rs,mid+1,r);
    update(x);
    return x;
}

int main()
{
    ios::sync_with_stdio(false);
    cin>>n>>m;
    int a,b,c;
    for(int ww=1;ww<n;ww++)
    {
        cin>>a>>b;
        tr[a].son.push_back(b);
        tr[b].son.push_back(a);
    }
    for(int ww=1;ww<=m;ww++)
    {
        cin>>a>>b>>c;
        change(root[a],1,100001,c,1);//树上差分
        change(root[b],1,100001,c,1);
        int lca_=get_lca(a,b);
        change(root[lca_],1,100001,c,-1);
        change(root[fa[lca_][0]],1,100001,c,-1);
    }
    for(int ww=n;ww>=1;ww--)//按dfn序依次向父节点合并
    {
        int now_=dfn_[ww];
        root[fa[now_][0]]=merge(root[fa[now_][0]],root[now_],1,100001);
    }
    for(int ww=1;ww<=n;ww++)
    {
        cout<<sgt[root[ww]].typ<<"\n";
    }
    return 0;
}

标签:int,线段,合并,sgt,fa,节点
From: https://www.cnblogs.com/sea-and-sky/p/18472887

相关文章

  • MySQL查询分组后如何分隔和聚合合并数据,来看这一篇文章就够了!
    博客主页:长风清留扬-CSDN博客系列专栏:MySQL入门到入魔每天更新大数据相关方面的技术,分享自己的实战工作经验和学习总结,尽量帮助大家解决更多问题和学习更多新知识,欢迎评论区分享自己的看法感谢大家点赞......
  • P1880 [NOI1995] 石子合并 区间DP 记忆化DFS模拟DP
    P1880[NOI1995]石子合并诈骗题,看着像贪心,实际上是DP对贪心的hack:6346542如果使用贪心法求最小得分,应该是如下的合并步骤:第一次合并3465422,3合并得分是5第二次合并546545,4合并得分是9第三次合并96545,4合并得分是9......
  • devexpress report 合并列具有重复值的单元格
    使用场景,分组统计产品数量,产品列每行都会重复出现相同的产品名,于是把此列所有相同内容的行的单元格合并成一个单元格,一些人觉得这样看的方便.不读文档一头雾水,试了很多次都没效果,看了文档发现非常简单.demo例子中没有,在官网中找到一个案例https://docs.devexpress.com/......
  • 代码随想录算法训练营day17| 654.最大二叉树 617.合并二叉树 700.二叉搜索树中的搜
    学习资料:https://programmercarl.com/0654.最大二叉树.html#算法公开课用前序遍历构造二叉树二叉搜索树的特点,其左节点的值<每个节点的值<其右节点的值,且根节点的值大于它的左子树的所有节点的值,小于它右子树的所有节点的值,其他同理。二叉搜索树的搜索和验证时不关心遍历顺序,因......
  • 数据合并和dplyr包的介绍
    数据合并选取数据newdata<-leadship[,c(1:6)]选取了q1到q5或者vars<-c("q1","q2","q3","q4","q5")Newdata<-leadship[,vars]>print(newdata)  q1q2q3q4q51 5 4 5 5 52 3 5 2 5 53 3 5 5......
  • 线段树
    不带lazy#include<bits/stdc++.h>usingnamespacestd;#definelllonglonglltr[1000000],a[1000000],ans[1000000],lazy[1000000];voidbui(llid,lll,llr){ if(l==r){ tr[id]=a[l]; return; } llmid=(l+r)>>1; bui(id*2,l,mid); bui(id*2+1,mi......
  • 合并两个排序的链表
    输入两个链表,并将它们的值按照递增的顺序合并成一个新的链表。题目要求如下:  我们可以创建两个新的链表,其中一个作为中间变量来存储合并后的链表,另一个链表记录中间链表并作为返回值返回。代码如下:/***structListNode{* intval;* structListNode*next;*}......
  • 计蒜客:斑点蛇(线段树)
     样例输入 1012345678910Query13Add36Query27Sub102Add63Query310End  样例输出 63359采用标准模板即可。注意线段树的节点个数一般为其范围的4倍。1#include<bits/stdc++.h>2usingnamespacestd;3vector<int>s(5000......
  • [区间dp]合并石子升级版
    题目描述还记得经典题石子合并吗?现在小YYY将题目加强啦!在一个圆形操场的四周摆放着nn......
  • 算法题——合并两个已排序链表(不使用额外空间)
    题目如下:        输入两个递增的链表,单个链表的长度为n,合并这两个链表并使新链表中的节点仍然是递增排序的。        数据范围: 0≤n≤10000≤n≤1000,−1000≤节点值≤1000−1000≤节点值≤1000要求:空间复杂度 O(1)O(1),时间复杂度 O(n)O(n)      ......