bzoj2212&3702 [Poi2011]Tree Rotations 二叉树 (线段树合并)

83 篇文章 0 订阅

bzoj2212&3702 [Poi2011]Tree Rotations 二叉树

原题地址
http://www.lydsy.com/JudgeOnline/problem.php?id=2212
http://www.lydsy.com/JudgeOnline/problem.php?id=3702

题意:
现在有一棵二叉树,所有非叶子节点都有两个孩子。在每个叶子节点上有一个权值(有n个叶子节点,满足这些权值为1..n的一个排列)。可以任意交换每个非叶子节点的左右孩子。
要求进行一系列交换,使得最终所有叶子节点的权值按照中序遍历写出来,逆序对个数最少。
这里写图片描述

数据范围
1<=n<=200000

题解:
注意到如果交换一个点的左右儿子,对它的每个儿子内部已经形成的逆序对数是没有影响的。
即:
子树u的逆序对数=ls的逆序对数+rs的逆序对数+跨越两子树的逆序对数

容易想到自底向上地计算每个点左右儿子交换和不交换的逆序对数取小的。

最简单粗暴的方式是枚举其中一棵子树的所有元素,分别统计另一棵子树中的大于它和小于它的个数,分别加进答案。
可以想到的方式是启发式合并,每次把小的儿子合并到大的儿子中,在这个过程中统计两种顺序的逆序对数,由此得到法一:
splay启发式合并,每次枚举小的那个子树,查另一棵子树中比他大的和比他小的。 回收空间。时间O(n log^2),空间O(n)

但实际上,可以考虑一个类似传统求逆序对的归并的过程。
在使左右两个序列有序地合在一起的过程中,自然地可以求到跨越左右的逆序对数。

这道题的做法是值域线段树合并,合并时自顶向下,这个复杂度是O(nlog)的。
(不会超过向其中插入N个节点的复杂度)

int merge(int x,int y)
{
    if(!x) return y; 
    if(!y) return x;
    ans1+=tr[tr[x].ls].sum*tr[tr[y].rs].sum;
    ans2+=tr[tr[x].rs].sum*tr[tr[y].ls].sum;
    tr[x].ls=merge(tr[x].ls,tr[y].ls);
    tr[x].rs=merge(tr[x].rs,tr[y].rs);
    update(x);
    del(y);
    return x;
}

(很像是一个cdq分治的处理。每次统计跨越中点的逆序对数)
时间复杂度O(nlogn),空间复杂度O(nlong)。

代码:

#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm> 
#include<vector>
#define LL long long
using namespace std;
const int N=400005;
int n,root[N],val[N],ch[N][2];
struct node
{
    int ls,rs;
    LL sum;
    void init() {ls=rs=0; sum=0LL;} 
}tr[N*30];
int pool[N*30],tail=0,now=0,rt;
LL ans1,ans2;
void build(int &nd)
{
    nd=++now;
    scanf("%d",&val[nd]); 
    if(!val[nd])
    {
        build(ch[nd][0]);
        build(ch[nd][1]);
    }
}
void update(int nd)
{
    int ls=tr[nd].ls; int rs=tr[nd].rs;
    tr[nd].sum=tr[ls].sum+tr[rs].sum;
}
void insert(int &nd,int lf,int rg,int pos)
{
    nd=pool[tail--];
    tr[nd].init();
    if(lf==rg)
    {
        tr[nd].sum=1LL; return;
    }
    int mid=(lf+rg)>>1;
    if(pos<=mid)  insert(tr[nd].ls,lf,mid,pos);
    else insert(tr[nd].rs,mid+1,rg,pos);
    update(nd);
}
void del(int x)
{
    tr[x].init();
    pool[++tail]=x;
    return;
}
int merge(int x,int y)
{
    if(!x) return y; 
    if(!y) return x;
    ans1+=tr[tr[x].ls].sum*tr[tr[y].rs].sum;
    ans2+=tr[tr[x].rs].sum*tr[tr[y].ls].sum;
    tr[x].ls=merge(tr[x].ls,tr[y].ls);
    tr[x].rs=merge(tr[x].rs,tr[y].rs);
    update(x);
    del(y);
    return x;
}
LL dfs(int nd)
{
    if(!nd) return 0;
    LL ans=0;
    if(!val[nd])
    {
        ans=dfs(ch[nd][0])+dfs(ch[nd][1]);
        ans1=ans2=0;
        root[nd]=merge(root[ch[nd][0]],root[ch[nd][1]]);
        ans+=min(ans1,ans2);    
    }
    else insert(root[nd],1,n,val[nd]);
    return ans;
}
int main()
{
    scanf("%d",&n); tr[0].init();
    for(int i=1;i<N*30;i++) pool[i]=++tail;
    build(rt);
    LL ans=dfs(1);
    printf("%lld\n",ans);
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值