【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


关于时间复杂度
• 整个过程的代价≤将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;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值