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;
}