BZOJ2212(线段树合并)

题意

TP

现在有一棵二叉树,所有非叶子节点都有两个孩子。在每个叶子节点上有一个权值(有n个叶子节点,满足这些权值为1..n的一个排列)。可以任意交换每个非叶子节点的左右孩子。
要求进行一系列交换,使得最终所有叶子节点的权值按照遍历序写出来,逆序对个数最少。

第一行n
下面每行,一个数x
如果x==0,表示这个节点非叶子节点,递归地向下读入其左孩子和右孩子的信息,
如果x!=0,表示这个节点是叶子节点,权值为x

1<=n<=200000

Sample Input

3
0
0
3
1
2

Sample Output

1

 题解

交换左右子树,只是这个节点的直接子数交换才对这个节点的逆序对数有影响。

每个节点的逆序对是左子树的逆序对的数量+右子树的逆序对数量+左子树对右子树的影响。

左子树对右子树的影响:右子树(l,mid)*左子树(mid+1,r)即为影响。

对两个子树建立权值线段树。不交换ans1 = cnt左[rs]*cnt右[ls],交换ans2cnt左[ls]*cnt右[rs]。

ans1,ans2取较小值作为此节点的贡献,这样自底向上合并就可以了。

 代码

#include
#include
#include
#include
#define LL long long
using namespace std;
const int maxn=8000010;
LL ans=0,ans1=0,ans2=0;
int n,r[maxn],root,ls[maxn],a[maxn];
int rs[maxn],s[maxn][2],ind=0,t[maxn];

void init_tree(int &x){  ///初始化这棵树
	x=++ind; scanf("%d",&a[x]);
	if (a[x]) return;
	init_tree(ls[x]);
	init_tree(rs[x]);
}

void push_up(int x){
	t[x]=t[s[x][0]]+t[s[x][1]];
}

void insert(int &x,int l,int r,int pos){ ///动态建线段树
	if (!x) x=++ind;
	if (l==r) {t[x]=1;return;}
	int mid=(l+r)>>1;
	if (pos<=mid) insert(s[x][0],l,mid,pos);
	else insert(s[x][1],mid+1,r,pos);
	push_up(x);
}

int merge(int x,int y){ ///合并线段树
	if (!x) return y; if (!y) return x;
	ans1+=1LL*t[s[x][1]]*t[s[y][0]];
	ans2+=1LL*t[s[x][0]]*t[s[y][1]];
	s[x][0]=merge(s[x][0],s[y][0]);
	s[x][1]=merge(s[x][1],s[y][1]);
	push_up(x); return x;
}

void solve(int x){
	if (a[x]) return;
	solve(ls[x]); solve(rs[x]);
	ans1=ans2=0;
	r[x]=merge(r[ls[x]],r[rs[x]]);
	ans+=min(ans1,ans2);
}

int main(){
	scanf("%d",&n);
	init_tree(root);
	for (int i=1;i<=ind;++i)
	    if (a[i]) insert(r[i],1,n,a[i]);

	solve(root);
	printf("%lld\n",ans);
}

 

你可能感兴趣的:(线段树,数据机构)