天天看點

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