权值线段树+线段树合并
我们对每一个节点开一棵权值线段树,那么考虑一个节点是否交换它的左右儿子。首先交换对左右子树内部的逆序对数无影响,对该子树与子树之外的逆序对也没有影响,于是只要考虑左右子树的逆序对数即可。考虑当前为权值线段树上的节点,(x,y)分别是左右子树上得到该节点,(L,R,sum)分别为线段树左右儿子以及该节点的数的个数,那么如果不交换,逆序对个数是(sum[R[x]]*sum[L[y]]),如果交换,那么逆序对个数是(sum[L[x]]*sum[R[y]]),那么只要我们在线段树合并的过程中把两个答案都统计出来,最后加上小的那一个就行了
说的可能有点不清楚,代码应该会比较好理解
另外这题的读入非常的迷……
第一行一个正整数$n$,表示该二叉树的叶节点的个数;
下面若干行,每行一个数$x$:
如果$x = 0$,表示这个节点不是叶节点,递归地向下读入其左孩子和右孩子的信息;
如果$x
eq 0$,表示这个节点是叶节点,权值为$x$。
//minamoto
#include<bits/stdc++.h>
#define ll long long
using namespace std;
#define getc() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<21,stdin),p1==p2)?EOF:*p1++)
char buf[1<<21],*p1=buf,*p2=buf;
template<class T>inline bool cmax(T&a,const T&b){return a<b?a=b,1:0;}
int read(){
int res,f=1;char ch;
while((ch=getc())>'9'||ch<'0')(ch=='-')&&(f=-1);
for(res=ch-'0';(ch=getc())>='0'&&ch<='9';res=res*10+ch-'0');
return res*f;
}
const int N=2e5+5;
int n,tot,L[N*20],R[N*20],val[N*20],tmp;ll ans,ans1,ans2;
void upd(int &p,int l,int r,int x){
if(!p)p=++tot;++val[p];if(l==r)return;
int mid=(l+r)>>1;
x<=mid?upd(L[p],l,mid,x):upd(R[p],mid+1,r,x);
}
void merge(int &x,int y){
if(!x||!y)return (void)(x+=y);
val[x]+=val[y];
ans1+=1ll*val[L[x]]*val[R[y]],ans2+=1ll*val[R[x]]*val[L[y]];
merge(R[x],R[y]),merge(L[x],L[y]);
}
void dfs(int &x){
int tmp=read(),ls,rs;x=0;
if(!tmp){
dfs(ls),dfs(rs),ans1=ans2=0;
x=ls,merge(x,rs),ans+=min(ans1,ans2);
}else upd(x,1,n,tmp);
}
int main(){
// freopen("testdata.in","r",stdin);
n=read(),dfs(tmp),printf("%lld
",ans);return 0;
}