线段树合并

先说说启发式合并

启发式合并可以看做是暴力的优化,一共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\n");continue;}
            int p=query(root[belong],1,n,y);
            printf("%d\n",id[p]);
        }
    }
} 
bzoj2733永无乡

线段树启发式合并

其实这个可能都不会怎么用到吧!毕竟时间是O(n*logn*logn)的,比递归式线段树合并多了一个log呢!

初始有n个只有一个叶子有值的线段树,在合并两个线段树的时候对于共有的节点把信息合并。
将含有数较少的线段数中的所有数插入另一个线段树,则最后把所有线段树合并成一颗的复杂度是O(nlognlogn)(假设提取数、信息合并是O(1)的)
证明:最糟糕的情况即完全二叉树,但SPLAY如此合并是O(nlogn)的只是常数很大

posted @ 2019-07-23 21:14  yyys  阅读(197)  评论(0编辑  收藏  举报