Tree Rotations
POI2011
题意
1.有一棵二叉树,所有非叶子节点都有两个孩子
2.每个叶子节点有一个权值(有n个叶子节点,这些权值是1~n的一个排列)
3.现在可以任意交换每个非叶子节点的左右孩子
4.要求进行一系列交换,使得最终所有叶子节点的权值按照遍历序写出来,逆序对个数最少
5.问逆序对个数最少为多少
解
方案1(树套树)
注意这个方法不能过Tree Rotations2 因为内存不够
1.对于每个点为根的树,求出这时的最小逆序对个数
2.可以随意交换这个点的两个叶子节点,那么子树内部的逆序对是没有影响的,只要考虑这两个子树之间的产生的贡献
3.主席树
每次遍历到叶子节点时,在主席树中加入这个点
4.启发式合并
枚举小树的点,(在主席树上)查询另一棵树上有多少个数比该点大
ans+=min(cnt,sz1*sz2-cnt)
具体代码
#include<bits/stdc++.h>
using namespace std;
const int M=200001;
long long ans;
int ID,n,A[M],len;
int LL[M*2],RR[M*2];
int tot,root[M],Ls[M*21],Rs[M*21],sum[M*21];
void build(int &rt,int L,int R) {
rt=++tot;
sum[rt]=0;
if(L==R)return;
int mid=L+R>>1;
build(Ls[rt],L,mid);
build(Rs[rt],mid+1,R);
}
void update(int &rt,int pt,int x,int L,int R) {
rt=++tot;
sum[rt]=sum[pt]+1;
Ls[rt]=Ls[pt],Rs[rt]=Rs[pt];
if(L==R)return;
int mid=L+R>>1;
if(x<=mid)update(Ls[rt],Ls[pt],x,L,mid);
else update(Rs[rt],Rs[pt],x,mid+1,R);
}
int query(int rt,int pt,int l,int r,int L,int R) {
if(l==L&&R==r)return sum[rt]-sum[pt];
int mid=L+R>>1;
if(r<=mid)return query(Ls[rt],Ls[pt],l,r,L,mid);
else if(mid<l)return query(Rs[rt],Rs[pt],l,r,mid+1,R);
else return query(Ls[rt],Ls[pt],l,mid,L,mid)+query(Rs[rt],Rs[pt],mid+1,r,mid+1,R);
}
int a;
int Sz(int x){
return RR[x]-LL[x]+1;
}
void dfs(int x) {
scanf("%d",&a);
if(a!=0) {
LL[x]=RR[x]=++len;
A[len]=a;
update(root[len],root[len-1],a,1,n);
} else {
int p1=++ID;
dfs(ID);
int p2=++ID;
dfs(ID);
LL[x]=LL[p1],RR[x]=RR[p2];
if(Sz(p1)>Sz(p2))swap(p1,p2);
//p1-->p2
long long cnt=0;
for(int i=LL[p1]; i<=RR[p1]; i++) {
cnt+=query(root[RR[p2]],root[LL[p2]-1],1,A[i],1,n);
}
ans+=min(cnt,1ll*Sz(p1)*Sz(p2)-cnt);
}
}
int main() {
scanf("%d",&n);
build(root[0],1,n);
dfs(ID=1);
printf("%lld\n",ans);
return 0;
}
方案2(Splay)
这个方法对于Tree Rotations2也适用,详见Tree Rotations2的题解
主要就是Splay省空间