[Poi2011]Tree Rotations 解题报告

来源:互联网 发布:java split第二个参数 编辑:程序博客网 时间:2024/06/08 17:30

拿这道题来学了一下线段树合并。
主要是照着这个课件学的,但是他那一句

整个过程的开销不会比向一棵空树顺序插入n个整数来的大

我完全没有看明白。。自己想了很久,终于想明白了。
之前有一句话是很关键的:

合并的开销正比于两棵树的公共节点数

所以我们考虑线段树中一个节点[l,r],它作为公共节点出现的次数是多少呢?显然是将[l,r]中所有元素合并的代价,就是r-l次!所以总的时间复杂度就是O(nlogn)。能不能有元素相同呢?能不能支持删除呢?当然是可以的。相同元素和删除元素的情况可以视为让一条链上的节点大小+1,所以显然没有问题。不过方便起见,我们下面讨论没有删除和没有相同元素的情况。
让我们再仔细算一下这个开销。实际上我们还会访问到公共节点的儿子节点,所以我们考虑一下一个节点被访问的次数,其实它等于它父亲节点作为公共节点被访问的次数。因为只要它父亲是公共节点,它就会被访问到;而如果不是的话,它就不会被访问到,所以这两者是等价的。而根节点的被访问到的次数就是n-1。再算上一开始的n棵线段树。我们知道线段树的树高是log2n+1,所以总访问次数就是n1+2(nlog2n(n1))+n(log2n+1)=3nlog2n+1。这里算的是一个非常紧的上界,当且仅当n=2k时可以取得这个上界。
我们为什么要算这么细呢?首先是为了分析这个玩意儿的常数,注意到它自带3的常数;其次是为了可持久化这个东西,我们知道可持久化是个超级烧内存的奢侈品,所以把内存算细是很有必要的。
但是如果我们不需要可持久化,比如说这道题,我们就可以只开n(log2n+1)的内存,合并两棵线段树的时候,直接把一棵合并到另一棵上就可以了。
课件里说什么mle。。我并不是很理解。而且我感觉如果3nlog2n+1mle了的话,他的搞法应该也会被一棵满二叉树卡掉吧。。
代码:

#include<cstdio>#include<iostream>using namespace std;#include<algorithm>#include<cstring>#include<cmath>const int N=2e5+5;typedef long long LL;int n,ptot=2;void in(int &x){    char c=getchar();    while(c<'0'||c>'9')c=getchar();    for(x=0;c>='0'&&c<='9';c=getchar())x=x*10+(c^'0');}const int Log=19;int size[N*Log],ls[N*Log],rs[N*Log],root[N<<1],ftot=1;LL cnt[N<<1][2];void build(int &node,int l,int r,int x){    node=ftot++;    size[node]=1;    //printf("build %d:[%d,%d]\n",node,l,r);    if(l!=r)        if(x<=l+r>>1)build(ls[node],l,l+r>>1,x);        else build(rs[node],(l+r>>1)+1,r,x);}void merge(int node,int &u,int v,int ucnt,int vcnt){    if(!u){        u=v;        cnt[node][0]+=ucnt*size[v];        return;    }    if(!v){        cnt[node][1]+=vcnt*size[u];        return;    }    merge(node,rs[u],rs[v],ucnt+size[ls[u]],vcnt+size[ls[v]]);    merge(node,ls[u],ls[v],ucnt,vcnt);    size[u]=size[ls[u]]+size[rs[u]];}LL ans;void dfs(int node){    int x;    in(x);    if(x==0){        int lson,rson;        dfs(lson=ptot++),dfs(rson=ptot++);        root[node]=root[lson];        //printf("---merge(%d,%d)---\n",lson,rson);        merge(node,root[node],root[rson],0,0);        ans+=min(cnt[node][0],cnt[node][1]);    }    else build(root[node],1,n,x);}int main(){    in(n);    dfs(1);    cout<<ans<<endl;}

总结:
①线段树合并的空间开销是:n(log2n+1)(不可持久化),3nlog2n+1(可持久化)。

0 0