现在有一棵二叉树,所有非叶子节点都有两个孩子。在每个叶子节点上有一个权值(有$n$个叶子节点,满足这些权值为$1..n$的一个排列)。可以任意交换每个非叶子节点的左右孩子。
要求进行一系列交换,使得最终所有叶子节点的权值按照中序遍历写出来,逆序对个数最少。
一个节点的子树内的交换与其无关,只与它的左右子树内有多少逆序对有关。所以可以贪心,满足交换一个节点的左右儿子后逆序对最少即可。用线段树合并既可维护子树内的权值,还可以在merge操作时直接求出逆序对数。舍我其谁啊。
两棵线段树合并时,递归的每一层都计算:
$x$左子树(小于$mid$个数)与$y$右子树(大于$mid$个数)之积,为交换后的逆序对;
$x$左子树(小于$mid$个数)与$y$右子树(大于$mid$个数)之积,为不交换的逆序对;
都累加之后取$min$的那个。
#include<bits/stdc++.h> using namespace std; typedef long long LL; const int N=400010; inline int read(){ int r=0,c=getchar(); while(!isdigit(c))c=getchar(); while(isdigit(c)) r=r*10+c-‘0‘,c=getchar(); return r; } struct Node{ int L,R,sum; }T[N*20]; int rt[N],sz,n,cnt; int ll[N],rr[N],val[N]; void getTree(int &x){ x=++cnt;val[x]=read(); if(val[x])return; getTree(ll[x]); getTree(rr[x]); } #define ls T[o].L #define rs T[o].R #define mid (l+r>>1) void pullup(int o){ T[o].sum=T[ls].sum+T[rs].sum; } void ins(int &o,int l,int r,int v){ o=++sz; if(l==r){ T[o].sum=1;return; } if(v<=mid)ins(ls,l,mid,v); else ins(rs,mid+1,r,v); pullup(o); } LL ans,ans1,ans2; int merge(int x,int y){ if(!x)return y; if(!y)return x; ans1+=1ll*T[T[x].L].sum*T[T[y].R].sum; ans2+=1ll*T[T[x].R].sum*T[T[y].L].sum; T[x].L=merge(T[x].L,T[y].L); T[x].R=merge(T[x].R,T[y].R); pullup(x); return x; } void dfs(int u){ if(val[u])return; dfs(ll[u]);dfs(rr[u]); ans1=ans2=0; rt[u]=merge(rt[ll[u]],rt[rr[u]]); ans+=ans1<ans2?ans1:ans2; } void init(){ n=read(); int r;getTree(r); for(int i=1;i<=cnt;i++) if(val[i])ins(rt[i],1,n,val[i]); } void solve(){ dfs(1); cout<<ans<<endl; } int main(){ init(); solve(); }