先说说启发式合并。
启发式合并可以看做是暴力的优化,一共n个元素,如果我们朴素的一个接一个合并,合并一次时间是O(n),要合并n次,时间是O(n^2)
但如果我们每次合并的时候,选择小的合并进大的,则最多合并logn次,时间是O(nlogn)
线段树合并
一般是合并值域线段树
初始有n个只有一个叶子有值的线段树,在合并两个线段树的时候对于共有的节点把信息合并。
对于不共有的节点直接返回,则最后把所有线段树合并成一颗的复杂度是O(nlgn)(假设信息合并是O(1)的)
证明:对于共有的部分,相当于删去了那么多点,最初有O(nlgn)个点,最后剩O(n)个点,没有新建点的操作,所以总复杂度是O(nlgn)的。
开空间的时候要开O(logn*n)
以下可以结合下面那道例题思考
假设一开始所有点都是独立的,存在自己的线段树中,值域1~n,开的点就应是logn的(可以看做是一条链),n棵就是n*logn。
int merge(int x,int y) { if(!x) return y; if(!y) return x; lc[x]=merge(lc[x],lc[y]); rc[x]=merge(rc[x],rc[y]); sum[x]=sum[lc[x]]+sum[rc[x]]; return x; }
bzoj2733永无乡可以用线段树合并解决
#include<bits/stdc++.h> #define N 100003 using namespace std; int read() { int x=0,f=1;char s=getchar(); while(s<'0'||s>'9'){if(s=='-')f=-1;s=getchar();} while(s>='0'&&s<='9'){x=x*10+s-'0';s=getchar();} return x*f; } int ndnum=0; int lc[N*20],rc[N*20],sum[N*20],root[N],fa[N],import[N],id[N]; //开的是值域线段树,每一个i一开始会建出一个1~n的值域线段树,高度logn,差不多是20 int getfa(int x) { if(fa[x]==x)return x; return fa[x]=getfa(fa[x]); } void insert(int &k,int l,int r,int val)//会修改,注意是&k(或者就用return的) { if(!k) k=++ndnum; if(l==r){sum[k]++;return;} int mid=(l+r)>>1; if(val<=mid)insert(lc[k],l,mid,val); else insert(rc[k],mid+1,r,val); sum[k]=sum[lc[k]]+sum[rc[k]]; } int query(int k,int l,int r,int rank) { if(l==r)return l; int mid=(l+r)>>1; if(rank<=sum[lc[k]])return query(lc[k],l,mid,rank); else { rank-=sum[lc[k]]; return query(rc[k],mid+1,r,rank); } } int merge(int x,int y) { if(!x) return y; if(!y) return x; lc[x]=merge(lc[x],lc[y]); rc[x]=merge(rc[x],rc[y]); sum[x]=sum[lc[x]]+sum[rc[x]]; return x; } char op[2]; int main() { int n=read(),m=read(); for(int i=1;i<=n;++i) fa[i]=i; for(int i=1;i<=n;++i) import[i]=read(),id[import[i]]=i; for(int i=1;i<=m;++i) { int a=read(),b=read(); int s1=getfa(a),s2=getfa(b); fa[s2]=s1; } for(int i=1;i<=n;++i) { int belong=getfa(i); insert(root[belong],1,n,import[i]); } int q=read(); while(q--) { scanf("%s",op); int x=read(),y=read(); if(op[0]=='B') { int s1=getfa(x),s2=getfa(y); if(s1==s2)continue; fa[s2]=s1; root[s1]=merge(root[s1],root[s2]); } else { int belong=getfa(x); if(sum[root[belong]]<y){printf("-1 ");continue;} int p=query(root[belong],1,n,y); printf("%d ",id[p]); } } }
线段树启发式合并
其实这个可能都不会怎么用到吧!毕竟时间是O(n*logn*logn)的,比递归式线段树合并多了一个log呢!
初始有n个只有一个叶子有值的线段树,在合并两个线段树的时候对于共有的节点把信息合并。
将含有数较少的线段数中的所有数插入另一个线段树,则最后把所有线段树合并成一颗的复杂度是O(nlognlogn)(假设提取数、信息合并是O(1)的)
证明:最糟糕的情况即完全二叉树,但SPLAY如此合并是O(nlogn)的只是常数很大