[bzoj2212][Poi2011]Tree Rotations

3702双倍经验。原题链接2212

现在有一棵二叉树,所有非叶子节点都有两个孩子。在每个叶子节点上有一个权值(有$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();
}
原文地址:https://www.cnblogs.com/orzzz/p/8094176.html