[POI2011]ROT-Tree Rotations

题意:给一棵n(1≤n≤200000个叶子的二叉树,可以交换每个点的左右子树,要求前序遍历叶子的逆序对最少。

算法见注释

#include<cstdio>
#define ll long long
using namespace std;
const int N=5e6+6;
int n,tot;
int lc[N],rc[N];
ll sz[N];
ll c1,c2,ans;

ll min(ll a,ll b){return a<b?a:b;}
int create(int v,int l=1,int r=n){
    int u=++tot;
    //build a ST for a single value, return u
    if(l==r)return sz[u]++, u;
    int mid=(l+r)>>1;
    if(v<=mid)lc[u]=create(v,l,mid);
    else rc[u]=create(v,mid+1,r);
    return sz[u]=sz[lc[u]]+sz[rc[u]], u;
}
int merge(int x,int y){//merge two ST, return new root
    if(!x||!y)return x+y;
    c1+=sz[rc[x]]*sz[lc[y]];
    c2+=sz[lc[x]]*sz[rc[y]];
    lc[x]=merge(lc[x],lc[y]), rc[x]=merge(rc[x],rc[y]);
    sz[x]=sz[lc[x]]+sz[rc[x]];
    return x;//y add to x
}
int calc(){//calc for subtree u, return the node of ST
    int a;
    scanf("%d",&a);
    if(a)return create(a);//a single node dont has c1bution.
    int x=calc(), y=calc();
    c1=0,c2=0;
    int z=merge(x,y);
    ans+=min(c1,c2);
    return z;
}
int main(){
    scanf("%d",&n);
    calc();
    printf("%lld
",ans);
    return 0;
}
/*
 * 求最少交换次数,注意到每个点的交换带来的影响是独立的,因此只考虑每个点
 * 是否交换。于是问题转化为求横跨两个兄弟结点子树中元素的逆序对数。
 * 把左树的元素添加进一个权值线段树,另一个树的元素在这棵树上做查询即可
 * 然后将另一个树的元素插入这个线段树,然后向上回溯即可
 * 事实上我们必然会建立一个右树的线段树,因此直接线段树合并即可
 * 你发现在合并的时侯就可以统计逆序对数了,于是查询的过程也省了
 * 即线段树合并
 * BUG#1: 没开LL
 * BUG#2: min函数忘开LL,下次还是用STD好
 */

原文地址:https://www.cnblogs.com/sshwy/p/10989318.html