bzoj3702二叉树 线段树合并

33 篇文章 0 订阅
3 篇文章 0 订阅

二倍经验:2212.
很久以前就打了这题,现在回来填坑。。
这题的话是标准的线段树合并。
线段树合并的话,对于一些(大部分)情况,比启发式合并要好很多,时间复杂度少一个log,实现也简单。。
这题来说的话,就是直接合并两个子树,子树中的答案=左子树中的逆序对+右子树中的逆序对+跨越左右的逆序对,前两个扫一遍就可以统计了,问题是最后那个跨越左右子树的逆序对。
假设我们现在合并两颗子树,一颗的根为x,另一颗为y。
由于在合并前,我们已经统计出了x,y的最小逆序对数,我们现在就要统计跨越他们之间的逆序对数量。
然后。。这里我们要对整棵树建权值线段树,保证我们合并的时候的逆序对可以直接计算。
统计逆序对的时候直接枚举是否交换就好了。


#include<cstdio>  
#include<cstring>  
#include<cstdlib>  
#include<cmath>  
#include<iostream>  
#include<algorithm>  
#define maxn 400010  
#define N 4000100  

using namespace std;  

int lch[N],rch[N],sum[N];  
int root[maxn],l[maxn],r[maxn],w[maxn];  
int n,m,num,tot;  
long long ans,cnt1,cnt2;  

void dfs(int x)  
{  
    scanf("%d",&w[x]);  
    if (!w[x])  
    {  
        l[x]=++num;  
        dfs(num);  
        r[x]=++num;  
        dfs(num);  
    }  
}  

void update(int x)  
{  
    sum[x]=sum[lch[x]]+sum[rch[x]];  
}  

void modify(int &x,int l,int r,int d)  
{  
    if (!x) x=++tot;  
    if (l==r) {sum[x]=1;return;}  
    int mid=(l+r)/2;  
    if (d<=mid) modify(lch[x],l,mid,d);  
    else modify(rch[x],mid+1,r,d);  
    update(x);  
}  

int merge(int x,int y)  
{  
    if (!x) return y;  
    if (!y) return x;  
    cnt1+=(long long)sum[rch[y]]*sum[lch[x]];  
    cnt2+=(long long)sum[rch[x]]*sum[lch[y]];  
    lch[x]=merge(lch[x],lch[y]);  
    rch[x]=merge(rch[x],rch[y]);  
    update(x);  
    return x;  
}  

void dfs1(int x)  
{  
    if (!w[x])  
    {  
        dfs1(l[x]);  
        dfs1(r[x]);  
        cnt1=cnt2=0;  
        root[x]=merge(root[l[x]],root[r[x]]);  
        ans+=min(cnt1,cnt2);  
    }  
}  

int main()  
{  

    scanf("%d",&n);  
    num++;dfs(1);  
    for (int i=1;i<=num;i++)  
      if (w[i]) modify(root[i],1,n,w[i]);  
    dfs1(1);  
    printf("%lld\n",ans);  
    return 0;  
}  
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值