[Poi2011]Tree Rotations 解题报告

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

整个过程的开销不会比向一棵空树顺序插入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+1 mle了的话,他的搞法应该也会被一棵满二叉树卡掉吧。。
代码:

#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
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值