1. 程式人生 > >[bzoj3702]二叉樹_線段樹

[bzoj3702]二叉樹_線段樹

中序 一道 scan cpp PE return 二叉 ace 進行

二叉樹 bzoj-3702

題目大意:現在有一棵二叉樹,所有非葉子節點都有兩個孩子。在每個葉子節點上有一個權值(有n個葉子節點,滿足這些權值為1到n的一個排列)。可以任意交換每個非葉子節點的左右孩子。
要求進行一系列交換,使得最終所有葉子節點的權值按照中序遍歷寫出來,逆序對個數最少。

註釋:$2\le n \le 2\cdot 10^5$。

想法:顯然,對於一個節點的兩個兒子lson和rson,無論lson和rson內部如何操作,任何兩個數x和y,滿足lson是x的祖先,rson是y的祖先且x>y,按照這樣構成的逆序對是不會因為內部操作而消失的,同樣地,也不會平白無故增加。所以一個節點pos的操作與不操作是獨立的,我們只需要將當前當前節點的兩顆子樹之間的逆序對數維護到最小,顯然就是最優解。這樣我們對每一個點維護左右逆序對數和區間和,然後自底向上操作。這個過程用線段樹

模擬。

最後,附上醜陋的代碼... ...

#include <iostream>
#include <cstdio>
using namespace std;
#define N 400010 
typedef long long ll;
int n,sz,seg;
ll ans,cnt1,cnt2;
int v[N],l[N],r[N],root[N];
int sum[N*10],ls[N*10],rs[N*10];
void readtree(int pos)
{
	scanf("%d",&v[pos]);
	if(!v[pos])
	{
		l[pos]=++sz;
		readtree(l[pos]);
		r[pos]=++sz;
		readtree(r[pos]);
	}
}
void pushup(int pos)
{
	sum[pos]=sum[ls[pos]]+sum[rs[pos]];
}
void build(int &pos,int l,int r,int val)
{
	if(!pos)pos=++seg;
	if(l==r){sum[pos]=1;return;}
	int mid=(l+r)>>1;
	if(val<=mid)build(ls[pos],l,mid,val);
	else build(rs[pos],mid+1,r,val);
	pushup(pos);
}
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 pos)
{
	if(!pos)return;
	solve(l[pos]);solve(r[pos]);
	if(!v[pos])
	{
		cnt1=cnt2=0;
		root[pos]=merge(root[l[pos]],root[r[pos]]);
		ans+=min(cnt1,cnt2);
	}
}
int main()
{
	scanf("%d",&n);
	++sz;
	readtree(1);
	for(int i=1;i<=sz;i++)
		if(v[i])build(root[i],1,n,v[i]);
	solve(1);
	printf("%lld\n",ans);
	return 0;
}

小結:超級喜歡的一道題,先從小引理入手,然後再進行一系列思考。

[bzoj3702]二叉樹_線段樹