线段树合并:从入门到放弃


  • 感谢这篇博客(这里跳转)以及邱宇大神的讲解,我也算作入门(自闭)了。
  • 需要掌握的前置知识点:动态开点线段树、权值线段树。
一、合并思想

线段树合并,就是指建立一颗新的线段树,保存原有的两颗线段树的信息。

那么就是:

假设现在合并到了线段树的a、b某一个pos

如果a在这个区间有pos,b没有,那么新的线段树pos位置赋值为a

如果b在这个区间有pos,a没有,那么新的线段树pos位置赋值为b

如果a、b都有,那么继续

如果此时处理到了两颗线段树的叶子节点,就把b在pos的值加到a上,把新线段树上的pos位置赋值为a

递归处理左子树

递归处理右子树

用左右子树的值更新当前节点

将新线段树上的pos位置赋值为a,返回

二、复杂度证明
  • 这个我确实不太会,所以直接借用一下大佬博客的吧。(咕咕咕)
三、题目
  • 例题1:CF600E
  • 题目很简单,但应该是第一次尝试打合并,没有独立打完代码,而是看着题解代码打的。算作板子吧。
#include<bits/stdc++.h>
#define ll long long
#define lson tr[pos].l
#define rson tr[pos].r
using namespace std;
const int N=1e5+10;
const int inf=1e5;
int cnt,tot,n,a[N],rt[N],ver[N<<1],Next[N<<1],lin[N];ll anss[N];
struct tree{
    int sum,val,l,r;ll ans;
}tr[N*20];
void add(int x,int y){ver[++tot]=y;Next[tot]=lin[x];lin[x]=tot;}
void maintain(int pos){
    if(tr[lson].sum>tr[rson].sum){
        tr[pos].val=tr[lson].val;
        tr[pos].sum=tr[lson].sum;
        tr[pos].ans=tr[lson].ans;
    }else if(tr[lson].sum<tr[rson].sum){
        tr[pos].val=tr[rson].val;
        tr[pos].sum=tr[rson].sum;
        tr[pos].ans=tr[rson].ans;
    }else{
        tr[pos].val=tr[lson].val;
        tr[pos].sum=tr[lson].sum;
        tr[pos].ans=tr[lson].ans+tr[rson].ans;
    }
    return;
}
int merge(int a,int b,int l,int r){
    if(!b||!a) return a+b;
    if(l==r){
        tr[a].val=l;
        tr[a].sum+=tr[b].sum;
        tr[a].ans=l;
        return a;
    }
    int mid=l+r>>1;
    tr[a].l=merge(tr[a].l,tr[b].l,l,mid);
    tr[a].r=merge(tr[a].r,tr[b].r,mid+1,r);
    maintain(a);
    return a;
}
void update(int &pos,int l,int r,int now,int v){
    if(!pos){
        pos=++cnt;
    }
    if(l==r){
        tr[pos].sum++;
        tr[pos].val=l;
        tr[pos].ans=l;
        return ;
    }
    int mid=l+r>>1;
    if(now<=mid) update(tr[pos].l,l,mid,now,v);
    else update(tr[pos].r,mid+1,r,now,v);
    maintain(pos);
}
void dfs(int x,int f){
    for(int i=lin[x];i;i=Next[i]){
        int y=ver[i];
        if(y==f) continue;
        dfs(y,x);
        merge(rt[x],rt[y],1,inf);
    }
    update(rt[x],1,inf,a[x],1);
    anss[x]=tr[rt[x]].ans;
}
int main(){
    scanf("%d",&n);
    for(int i=1;i<=n;++i) scanf("%d",&a[i]),rt[i]=++cnt;
    for(int i=1;i<n;++i){
        int x,y;scanf("%d%d",&x,&y);
        add(x,y);add(y,x);
    }
    dfs(1,0);
    for(int i=1;i<=n;++i) printf("%I64d ",anss[i]);
    cout<<endl;
    return 0;
}

题目先咕着,有空写。

以上

原文地址:https://www.cnblogs.com/kgxw0430/p/10489121.html

时间: 2024-08-29 22:24:09

线段树合并:从入门到放弃的相关文章

超全面的线段树:从入门到入坟

超全面的线段树:从入门到入坟 \(Pre\):其实线段树已经学了很久了,突然线段树这个数据结构比较重要吧,现在想写篇全面的总结,帮助自己复习,同时造福广大\(Oier\)(虽然线段树的思维难度并不高).本篇立志做一篇最浅显易懂,最全面的线段树讲解,采用\(lyd\)写的<算法竞赛进阶指南>上的顺序,从最基础的线段树到主席树,本篇均会涉及,并且附有一定量的习题,以后可能会持续更新,那么现在开始吧! 目录 更新日志 线段树想\(AC\)之基本原理(雾*1 线段树想偷懒之懒标记(雾*2 线段树想应用

【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

bzoj 4631: 踩气球 线段树合并

4631: 踩气球 Time Limit: 10 Sec  Memory Limit: 256 MBSubmit: 265  Solved: 136[Submit][Status][Discuss] Description 六一儿童节到了, SHUXK 被迫陪着M个熊孩子玩一个无聊的游戏:有N个盒子从左到右排成一排,第i个盒子里装着Ai个气球. SHUXK 要进行Q次操作,每次从某一个盒子里拿出一个没被踩爆的气球,然后熊孩子们就会立刻把它踩爆. 这M个熊孩子每个人都指定了一个盒子区间[Li, R