题目链接2212:[Poi2011]Tree Rotations
题意:
给定一颗完全二叉树,每个叶子节点有一个权值,你可以任意交换每个点的左右儿子,使得最后整棵树中序遍历的逆序对个数最少
搬运题解系列:
考虑一个节点的左右子树的子树是否交换过对这个节点的逆序对数目没有影响,只有这个节点直接的子树交换才会产生影响
那么我们可以分治的去计算每个节点的贡献,然后向上传递
每个节点的逆序对是左子树的逆序对的数量+右子树的逆序对数量+跨越子树的逆序对数量
我们交换子树更改的就是最后那个跨越子树的逆序对数量,那么:
如果我们交换了左右子树,跨越子树的逆序对数量为没交换时左子树中<mid的数的数量*右子树中>=mid的数的数量
如果我们没有交换左右子树,那么逆序对数量就是左子树中>=mid的数的数量*右子树中<mid的数的数量
然后我们向上传递信息。这一步用线段树合并即可。
PS:第一道线段树合并,需要好好理解!
#include<bits/stdc++.h>
using namespace std;
#define LL long long
#define N 400005
#define MaxNode 4000005
int n, rt, Index, IndexT;
int a[MaxNode], root[MaxNode], ls[MaxNode], rs[MaxNode], Tree[MaxNode], son[MaxNode][2];
//root以每个节点建立的权值线段树的根
//ls树的左儿子,rs树的右儿子
//Tree所有节点的权值线段树
//son以i号节点建立的权值线段树的编号,合并后就为i号节点及其子节点的权值线段树的节点编号
LL ans1, ans2, ans;
void Read_Tree(int &x)
{
x=++IndexT;
scanf("%d", &a[x]);
if(a[x]!=0) return ;
Read_Tree(ls[x]);
Read_Tree(rs[x]);
}
void push_up(int x)
{
Tree[x]=Tree[son[x][0]]+Tree[son[x][1]];
}
void Insert(int &x, int l, int r, int pos)
{
if(x==0) x=++Index;
if(l==r)
{
Tree[x]=1;
return ;
}
int mid=(l+r)>>1;
if(pos<=mid) Insert(son[x][0], l, mid, pos);
else Insert(son[x][1], mid+1, r, pos);
push_up(x);
}
int Merge(int x, int y)
{
if(!x) return y;
if(!y) return x;
ans1+=(LL)Tree[son[x][1]]*(LL)Tree[son[y][0]];
ans2+=(LL)Tree[son[x][0]]*(LL)Tree[son[y][1]];
son[x][0]=Merge(son[x][0], son[y][0]);
son[x][1]=Merge(son[x][1], son[y][1]);
push_up(x);
return x;
}
void solve(int x)
{
if(a[x]) return ;
solve(ls[x]);solve(rs[x]);
ans1=ans2=0;
root[x]=Merge(root[ls[x]], root[rs[x]]);
ans += min(ans1, ans2);
}
int main()
{
scanf("%d", &n);
Read_Tree(rt);
for(int i=1; i<=IndexT; i++)
if(a[i]!=0) Insert(root[i], 1, n, a[i]);
solve(rt);
cout<<ans<<endl;
return 0;
}