1. 程式人生 > >【bzoj3702】二叉樹 權值線段樹

【bzoj3702】二叉樹 權值線段樹

神奇的解法
對於每個節點,建出權值線段樹
每次查詢右子樹的權值線段樹和左子樹的權值線段樹,左子樹中比右子樹小的有多少?右子樹比左子樹小的有多少?(分別對應不交換的逆序對和交換的逆序對)
將左子樹和右子樹的權值線段樹合併
遞迴進行這個操作
感覺複雜度很不靠譜,於是想證明一下複雜度
最開始權值線段樹共O(nlogn)個節點,最後共O(n)個節點
每次合併兩棵樹的每個節點都要訪問一遍,所以每個節點好像是要訪問O(dep[i])次?
但是,合併兩棵樹後,有些重複的節點被合併到了一起
所以好像有些節點又沒有合併O(dep[i])次?

感覺很靠譜,但是不會證明

#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<cmath>
#include<iostream>
#include<algorithm>
#define maxn 400010
#define N 4000100

using namespace std;

int lch[N],rch[N],sum[N];
int root[maxn],l[maxn],r[maxn],w[maxn];
int n,m,num,tot;
long long ans,cnt1,cnt2;

void dfs(int x)
{
	scanf("%d",&w[x]);
	if (!w[x])
	{
		l[x]=++num;
		dfs(num);
		r[x]=++num;
		dfs(num);
	}
}

void update(int x)
{
	sum[x]=sum[lch[x]]+sum[rch[x]];
}

void modify(int &x,int l,int r,int d)
{
	if (!x) x=++tot;
	if (l==r) {sum[x]=1;return;}
	int mid=(l+r)/2;
	if (d<=mid) modify(lch[x],l,mid,d);
	else modify(rch[x],mid+1,r,d);
	update(x);
}

int merge(int x,int y)
{
	if (!x) return y;
	if (!y) return x;
	cnt1+=(long long)sum[rch[y]]*sum[lch[x]];
	cnt2+=(long long)sum[rch[x]]*sum[lch[y]];
	lch[x]=merge(lch[x],lch[y]);
	rch[x]=merge(rch[x],rch[y]);
	update(x);
	return x;
}

void dfs1(int x)
{
	if (!w[x])
	{
		dfs1(l[x]);
		dfs1(r[x]);
		cnt1=cnt2=0;
		root[x]=merge(root[l[x]],root[r[x]]);
		ans+=min(cnt1,cnt2);
	}
}

int main()
{
	scanf("%d",&n);
	num++;dfs(1);
	for (int i=1;i<=num;i++)
	  if (w[i]) modify(root[i],1,n,w[i]);
	dfs1(1);
	printf("%lld\n",ans);
	return 0;
}