1. 程式人生 > >BZOJ2212&3702: [Poi2011]Tree Rotations

BZOJ2212&3702: [Poi2011]Tree Rotations

題目大意:給定一顆完全二叉樹,每個葉子節點有一個權值,你可以任意交換每個點的左右兒子,使得最後整棵樹中序遍歷的逆序對個數最少 交換左右兒子不會影響左右兒子子樹中的逆序對個數,只會影響這兩顆子樹之間的逆序對個數,而左右兒子子樹中是否交換左右兒子不會影響這兩顆字數之間的逆序對個數,所以我們可以對每個結點的子樹分開考慮,所有節點的兩個子樹間的逆序對個數總和就是整個樹的逆序對個數總和 然後對於每個節點,我們就選擇兩種方式中逆序對個數小的方式,這一步可以用線段樹合併(或者splay啟發式合併)來在O(NlogN)的時間複雜度內計算出來答案
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define N 4000010
using namespace std;
long long a[N],cnt;
long long ch[N][2];
long long rt[N],cn,CH[N][2],sum[N],CNT;
long long n;
void addnew(long long &x,long long l,long long r,long long v)
{
	CNT++;x=CNT;
	sum[x]++;
	if(l==r) return;
	long long mid=(l+r)>>1;
	if(v<=mid) addnew(ch[x][0],l,mid,v);
	else addnew(ch[x][1],mid+1,r,v);
}
void build(long long &x)
{
	cnt++;x=cnt;
	scanf("%lld",&a[x]);
	if(a[x]!=0)
	{
		addnew(rt[x],1,n,a[x]);
		return;
	}
	build(CH[x][0]);
	build(CH[x][1]);
}
long long ans,ANS;
long long merge(long long x,long long y)

{
	if(!x) return y;
	if(!y) return x;
	ans+=sum[ch[x][0]]*sum[ch[y][1]];
	ch[x][0]=merge(ch[x][0],ch[y][0]);
	ch[x][1]=merge(ch[x][1],ch[y][1]);
	sum[x]=sum[ch[x][0]]+sum[ch[x][1]];
	return x;
}
void dfs(long long x)
{
	long long lc=CH[x][0],rc=CH[x][1];
	if(a[x]) return;
	dfs(lc);dfs(rc);
	long long tot=sum[rt[lc]]*sum[rt[rc]];
	ans=0;
	rt[x]=merge(rt[lc],rt[rc]);
	ANS+=min(ans,tot-ans);
}
int main()
{
	scanf("%lld",&n);
	long long root;
	build(root);
	dfs(root);
	printf("%lld",ANS);
}