线段树合并
适用场景
• 树上问题:在树上需要合并子树信息时,可以使用线段树合并来优化时间复杂度。
• 动态开点:通常与动态开点的线段树结合使用,以减少空间消耗。
算法流程
- 递归合并:从根节点开始,同时遍历两棵线段树。
- 空节点处理:如果某个位置的节点在一棵树中为空,则直接使用另一棵树中的节点。
- 叶子节点处理:到达叶子节点时,直接合并该节点的信息。
- 非叶子节点处理:对于非叶子节点,递归合并左右子树,并更新当前节点的信息。
时间复杂度
• 线段树合并的时间复杂度为 O(nlogn),其中 n是有效节点的总数。这是因为每次合并操作只会遍历两棵树共有的节点,避免了不必要的递归。
实现技巧
• 标记永久化:在某些情况下,可以使用标记永久化来简化区间修改的操作。
• 动态开点:只在需要时创建节点,可以显著减少空间消耗。
应用示例
• 树上差分:结合线段树合并,可以在树上进行区间修改和查询操作。(转区间操作为单点操作,适用于树上每个节点都要维护一个线段树的题目)
• 集合合并:维护每个集合的权值线段树,通过合并线段树来合并集合。
例题
1.洛谷P4556 (树上差分+线段树合并)
2.洛谷P1600 (也是树上差分+线段树合并,有一定思维难度)
洛谷P1600题解
1.处理好每个人跑步过程的lca和dis
2.不能从每个人每一步会经过哪些观察员这个视角去做题,要从每个人过程中会给什么情况的观察员做贡献去考虑
3.我们记运动员从S到T,观察员位于j点,i点在树上的深度为dep[i]
通过推到得到,对于S到lca的路径,若观察员满足dep[S]-dep[j]=w[j],也就是dep[S]=dep[j]+w[j],那么该运动员会对其产生1的贡献
对于lca到T的过程,若观察员满足dep[t]-dep[j]=dis-w[j],也就是dep[t]-dis=dep[j]-w[j],那么该运动员会对其产生1的贡献
4.那这样我们就可以通过树上差分转化路径加1为单点加1,然后发现对于每个观察员,所能给他做贡献的S和T都在其子树内部(很合理,因为无论是S,还是T到lca都是一个向上走的过程)(要保证不会计算重复,我们对于两种贡献,不能把lca点处理两遍),所以就可以通过单点修改到最后进行线段树合并得到每个点的值了
#include<bits/stdc++.h>
#define pb push_back
#define mid (l+(r-l)/2)
#define ls(op,p) d[op][p].ls
#define rs(op,p) d[op][p].rs
using namespace std;
const int N=300010;
int n,m;
vector<int> g[N];
int w[N];
int ans[N];
struct PER{
int s,t;
}per[N];
int dep[N],size[N],son[N],fa[N];
int top[N];
void dfs1(int s,int f){
fa[s]=f; size[s]=1; dep[s]=dep[f]+1;
for(int to:g[s]){
if(to==f) continue;
dfs1(to,s);
if(size[son[s]]<size[to]) son[s]=to;
size[s]+=size[to];
}
}
void dfs2(int s,int Top){
top[s]=Top;
if(son[s]) dfs2(son[s],Top);
for(int to:g[s]){
if(to==fa[s]||to==son[s]) continue;
dfs2(to,to);
}
}
int LCA(int x,int y){
while(top[x]!=top[y]){
if(dep[top[x]]<dep[top[y]]) swap(x,y);
x=fa[top[x]];
}
if(dep[x]<dep[y]) swap(x,y);
return y;
}
void init(){
dfs1(1,0);
dfs2(1,1);
}
struct node{
int ls,rs;
int cnt;
}d[2][30*N];
int root[2][N],tot;
void up(int op,int p){
d[op][p].cnt=d[op][ls(op,p)].cnt+d[op][rs(op,p)].cnt;
}
void change(int &p,int l,int r,int pos,int num,int op){//单点修改
if(!p) p=++tot;
if(l==r){
d[op][p].cnt+=num;
return;
}
if(pos<=mid) change(ls(op,p),l,mid,pos,num,op);
else change(rs(op,p),mid+1,r,pos,num,op);
up(op,p);
}
int query(int p,int l,int r,int pos,int op){
if(pos>r||!p) return 0;
if(l==r) return d[op][p].cnt;
if(pos<=mid) return query(ls(op,p),l,mid,pos,op);
else return query(rs(op,p),mid+1,r,pos,op);
}
int merge(int x,int y,int l,int r,int op){//线段树合并,并且取值
if(!x||!y) return x+y;
if(l==r){
d[op][x].cnt+=d[op][y].cnt;
return x;
}
ls(op,x)=merge(ls(op,x),ls(op,y),l,mid,op);
rs(op,x)=merge(rs(op,x),rs(op,y),mid+1,r,op);
up(op,x);
return x;
}
void calc(int s,int f){
for(int to:g[s]){
if(to==f) continue;
calc(to,s);
root[0][s]=merge(root[0][s],root[0][to],1,n,0);
root[1][s]=merge(root[1][s],root[1][to],1,2*n,1);
}
ans[s]=query(root[0][s],1,n,dep[s]+w[s],0)+query(root[1][s],1,2*n,dep[s]-w[s]+n,1);
}
void solve(){
for(int i=1;i<=m;i++){
int lca=LCA(per[i].s,per[i].t);
int dis=dep[per[i].s]+dep[per[i].t]-2*dep[lca];
//对s到lca的贡献进行统计
int pos1=dep[per[i].s];//将要单点修改的值域点
//树上差分
change(root[0][per[i].s],1,n,pos1,1,0);
change(root[0][fa[lca]],1,n,pos1,-1,0);
//对lca到s的贡献进行统计
int pos2=dep[per[i].t]-dis+n;
change(root[1][per[i].t],1,2*n,pos2,1,1);
change(root[1][lca],1,2*n,pos2,-1,1);
}
calc(1,0); //对差分进行前缀和复原
}
int main(){
//freopen(".in","r",stdin);
//freopen(".out","w",stdout);
ios::sync_with_stdio(0);
cin>>n>>m;
for(int i=1;i<n;i++){
int u,v;
cin>>u>>v;
g[u].pb(v);
g[v].pb(u);
}
for(int i=1;i<=n;i++) cin>>w[i];
init();
for(int i=1;i<=m;i++) cin>>per[i].s>>per[i].t;
solve();
for(int i=1;i<=n;i++) cout<<ans[i]<<' ';
return 0;
}

浙公网安备 33010602011771号