题目大意
有一个n个叶子结点的树,叶子结点上有权值,且为[1,n]的排列。
你可以交换任一非叶子结点的左右儿子,请最小化中序遍历后的逆序对个数。
线段树合并
显然在一个非叶子结点需要确定左右次序,并且这与其左右子树内的次序无关。
对于每个结点维护一个线段树,那么每次就是合并左右儿子的线段树。
至于如何统计每种次序的逆序对个数,线段树合并的时候统计就好了,具体见代码。
#include<cstdio>
#include<algorithm>
#define fo(i,a,b) for(i=a;i<=b;i++)
using namespace std;
typedef long long ll;
const int maxn=200000+10,maxd=200000*20+10;
int root[maxn*2],sum[maxd],left[maxd],right[maxd],tree[maxn*2][2];
int i,j,k,l,t,n,m,tot,top;
ll ans,cnt1,cnt2;
void insert(int &x,int l,int r,int a){
if (!x) x=++tot;
if (l==r){
sum[x]++;
return;
}
int mid=(l+r)/2;
if (a<=mid) insert(left[x],l,mid,a);else insert(right[x],mid+1,r,a);
sum[x]=sum[left[x]]+sum[right[x]];
}
int merge(int x,int y,int l,int r){
if (!x||!y) return x+y;
if (l==r){
sum[x]+=sum[y];
return x;
}
int mid=(l+r)/2;
cnt1+=(ll)sum[left[x]]*sum[right[y]];
cnt2+=(ll)sum[right[x]]*sum[left[y]];
left[x]=merge(left[x],left[y],l,mid);
right[x]=merge(right[x],right[y],mid+1,r);
sum[x]=sum[left[x]]+sum[right[x]];
return x;
}
void dfs(int x){
scanf("%d",&t);
if (t) insert(root[x],1,n,t);
else{
tree[x][0]=++top;
dfs(tree[x][0]);
tree[x][1]=++top;
dfs(tree[x][1]);
cnt1=cnt2=0;
root[x]=merge(root[tree[x][0]],root[tree[x][1]],1,n);
ans+=min(cnt1,cnt2);
}
}
int main(){
scanf("%d",&n);
dfs(top=1);
printf("%lld\n",ans);
}