天天看点

【bzoj2212】[Poi2011]Tree Rotations(线段树的合并)(主席树-可持久化线段树)

 POI 18 Tree Rotations

• http://main.edu.pl/en/archive/oi/18/rot

题目大意:

• 给定一棵2n-1个节点的二叉树,每个叶子上有1~n的数字,保证每

个数字出现且仅出现一次

• 允许任意次交换某两棵兄弟子树

• 对交换完毕的树求先序遍历,形成1~n的一个排列

• 求这个排列最小的逆序对个数

• 1 ≤ n ≤ 2 * 1e5 (1e6)

思路:

T (不是叶子)的逆序对由三部分组成

• 1. 左子树的逆序对个数

• 2. 右子树的逆序对个数

• 3. 集合 {(a, b) | a > b, a属于左子树, b属于右子树} 的大小

• 1 & 2可以递归处理变为3

• 于是,自底向上,对每个子树的根判断交换左右子树是否会更优

 平衡树 (Splay) 维护子树内数字的有序排列

• 自底向上对Splay进行启发式合并, 合并时计算出3

• O(n log^2(n) )

• 复杂度仍然不足以AC (1e6)

线段树的合并

• 前提,两个线段树的范围相同

• merge(a, b):

• 如果a, b中有一个为空,返回另一个

• 如果a, b都是叶子节点,merge_leaf(a, b)

• 返回merge(a->l, b->l)与merge(a->r, b->r)连接形成的树的根

• 动态开点

• Example

【bzoj2212】[Poi2011]Tree Rotations(线段树的合并)(主席树-可持久化线段树)

关于时间复杂度

• 整个过程的代价≤将n棵只有单个节点的线段树合并成同一棵树代

• 复杂度为O (n logn)

 回到此题,可以用一些统计区间内数字个数 (cnt) 的线段树的合并

来解决

• 在merge的过程中统计交换与不交换产生的逆序对数

• a属于T的左子树,b属于T的右子树,ans0为不交换产生的逆序对

数,ans1为交换产生的逆序对数

• merge(a, b):

• 如果a, b中有一个为空,就返回另一个

• ans0 += cnt(a -> r) * cnt(b -> l)

• ans1 += cnt(a -> l) * cnt(b -> r)

• 返回merge(a->l, b->l)与merge(a->r, b->r)连接形成的树的根

• “如果a, b都是叶子节点,merge_leaf(a, b)” ?

• 不存在两个相同元素,a, b不可能同为叶子节点

• 时间复杂度O(n logn)

• 空间复杂度O(n)

• MLE?

• 注意回收内存

• 先递归较大的子树

#include<iostream>
#include<cstdio>
#define ll long long 
using namespace std;
inline int read()
{
	int x=0,f=1;char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
	return x*f;
}
int n,sz,seg;
ll ans,cnt1,cnt2;
int v[400005],l[400005],r[400005],root[400005];
int sum[4000005],ls[4000005],rs[4000005];
void readtree(int x)
{
	v[x]=read();
	if(!v[x])
	{
		l[x]=++sz;
		readtree(l[x]);
		r[x]=++sz;
		readtree(r[x]);
	}
}
void pushup(int k)
{
    sum[k]=sum[ls[k]]+sum[rs[k]];
}
void build(int &k,int l,int r,int val)
{
	if(!k)k=++seg;
	if(l==r){sum[k]=1;return;}
	int mid=(l+r)>>1;
	if(val<=mid)build(ls[k],l,mid,val);
	else build(rs[k],mid+1,r,val);
	pushup(k);
}
int merge(int x,int y)
{
	if(!x)return y;
	if(!y)return x;
	cnt1+=(ll)sum[rs[x]]*sum[ls[y]];
	cnt2+=(ll)sum[ls[x]]*sum[rs[y]];
	ls[x]=merge(ls[x],ls[y]);
	rs[x]=merge(rs[x],rs[y]);
	pushup(x);
	return x;
}
void solve(int x)
{
	if(!x)return;
	solve(l[x]);solve(r[x]);
	if(!v[x])
	{
		cnt1=cnt2=0;
		root[x]=merge(root[l[x]],root[r[x]]);
		ans+=min(cnt1,cnt2);
	}
}
int main()
{
	n=read();++sz;
	readtree(1);
	for(int i=1;i<=sz;i++)
		if(v[i])build(root[i],1,n,v[i]);
	solve(1);
	printf("%lld",ans);
	return 0;
}