线段树合并
线段树合并就是把两个维护相同区间的线段树合并到一块.
什么叫维护相同区间?就是每次操作的最大的那个区间是一样的,根节点维护的区间一样,根节点的左儿子维护的也一样,右儿子维护的也一样,左儿子的左儿子....
显然只需要建树的时候都建成一样的就好了.一般我们采用动态开点.
由于线段树合并的均摊复杂度是log的,虽然我看不懂证明可以开成权值线段树.
对于两个线段树该如何合并呢?我们递归的定义:
1.对于线段树a,b.如果只有a有当前区间或只有b有当前区间,那么把那个认定为新线段树的节点.
2.否则我们同时递归a的左儿子和b的左儿子,递归完后把刚认定的节点作为a的左儿子.
3.递归a的右儿子和b的右儿子,递归完后把刚认定的节点作为a的右儿子.
4.把a认定为新线段树的节点.
如果有两个满的线段树,每次复杂度是O(n)还多的.但是如果刚开始有n个线段树,每个线段树用动态开点维护一个数,那么合并n-1次后均摊复杂度是nlogn的.
校内oj并没有找到例题.我口述一个例题好了.
给你一棵有n个点的树,树上每个节点都有一种颜色 ci,求以每个点为根的子树出现最多的颜色的和.
这是一个最简单的线段树合并了.先对每个节点的每个颜色做一个权值线段树,从根dfs整颗树.dfs完儿子后,合并父亲和儿子的权值线段树,记录答案.没了.
口胡还是开心的.我又写了一道题以供解析.
https://www.luogu.org/problemnew/show/P3224.
询问的是与x相连通的所有岛中第k重要的.假如没有修改操作,可以对每个连通块弄一个权值线段树,每次询问是logn对吧.现在修改操作相当于把两个连通块联通,我们想到可以写线段树合并.
想到连通块就会想到并查集.为了解决 "如果xy原来就联通,合并线段树又错误了"这个问题,写一个并查集即可.
int n,m,tot; int rt[100010],fa[100010]; struct tree { int l,r,sum,ans; }tr[2000010]; int get(int x) { return fa[x]==x?x:fa[x]=get(fa[x]); } void New(int &now,int l,int r,int x,int id) { if(!now) now=++tot; if(l==r) { tr[now].sum++; tr[now].ans=id; return ; } int mid=l+r>>1; if(x<=mid) New(tr[now].l,l,mid,x,id); else New(tr[now].r,mid+1,r,x,id); tr[now].sum=tr[tr[now].l].sum+tr[tr[now].r].sum; } int merge(int a,int b,int l,int r) { if(!a||!b) return a+b; if(l==r) { tr[a].sum+=tr[b].sum; tr[a].ans=max(tr[a].ans,tr[b].ans); 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); tr[a].sum=tr[tr[a].l].sum+tr[tr[a].r].sum; return a; } int ask(int now,int l,int r,int x) { if(x>tr[now].sum) return -1; if(l==r) return tr[now].ans; int mid=l+r>>1; if(tr[tr[now].l].sum>=x) return ask(tr[now].l,l,mid,x); else return ask(tr[now].r,mid+1,r,x-tr[tr[now].l].sum); } int main() { //freopen("123.in","r",stdin); n=read(),m=read(); for(int i=1;i<=n;++i) fa[i]=i; for(int i=1;i<=n;++i) New(rt[i],1,n,read(),i); for(;m;m--) { int x=get(read()),y=get(read()); if(x==y) continue; else { merge(rt[x],rt[y],1,n); fa[y]=x; } } for(int Q=read();Q;Q--) { char ch;cin>>ch; if(ch=='Q') { int x=get(read()); write(ask(rt[x],1,n,read())); } else { int x=get(read()),y=get(read()); if(x==y) continue; else { merge(rt[x],rt[y],1,n); fa[y]=x; } } } return 0; }
个人认为线段树合并好想好写.是一个线段树思想的拓展.很有意义吧.