P3521 [POI2011]ROT-Tree Rotations

传送门

权值线段树+线段树合并

我们对每一个节点开一棵权值线段树,那么考虑一个节点是否交换它的左右儿子。首先交换对左右子树内部的逆序对数无影响,对该子树与子树之外的逆序对也没有影响,于是只要考虑左右子树的逆序对数即可。考虑当前为权值线段树上的节点,(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;
}
原文地址:https://www.cnblogs.com/bztMinamoto/p/9968700.html