BZOJ2212 [Poi2011]Tree Rotations 【线段树合并】

时间:2023-03-09 08:36:57
BZOJ2212 [Poi2011]Tree Rotations  【线段树合并】

题目链接

BZOJ2212

题解

一棵子树内的顺序不影响其与其它子树合并时的答案,这一点与归并排序的思想非常相似

所以我们只需单独处理每个节点的两棵子树所产生的最少逆序对即可

只有两种情况,要么正序要么逆序,且这两种情况数目是互补的

如果左子树大小为\(S_l\),右子树大小为\(S_r\),那么总对数为\(S_lS_r\)

如何快速统计一棵子树中大于另一棵子树中权值的对数?

开一个权值线段树,在线段树合并过程中统计即可

由于权值是一个排列,所以复杂度是\(O(nlogn)\)

顺带一提,左右儿子都满的二叉树节点数\(n = 2s - 1\),\(s\)表示叶子的个数

可以用数归证明

所以节点数最多不超过\(4 \times 10^5\)

#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
#include<cmath>
#include<map>
#define Redge(u) for (int k = h[u],to; k; k = ed[k].nxt)
#define REP(i,n) for (int i = 1; i <= (n); i++)
#define mp(a,b) make_pair<int,int>(a,b)
#define cls(s) memset(s,0,sizeof(s))
#define cp pair<int,int>
#define LL long long int
using namespace std;
const int maxn = 400005,maxm = 10000005,INF = 1000000000;
inline int read(){
int out = 0,flag = 1; char c = getchar();
while (c < 48 || c > 57){if (c == '-') flag = -1; c = getchar();}
while (c >= 48 && c <= 57){out = (out << 3) + (out << 1) + c - 48; c = getchar();}
return out * flag;
}
int n,N,rt[maxn],sum[maxm],ls[maxm],rs[maxm],cnt;
LL ans,tot;
void modify(int& u,int pre,int l,int r,int pos){
sum[u = ++cnt] = sum[pre] + 1;
ls[u] = ls[pre]; rs[u] = rs[pre];
if (l == r) return;
int mid = l + r >> 1;
if (mid >= pos) modify(ls[u],ls[pre],l,mid,pos);
else modify(rs[u],rs[pre],mid + 1,r,pos);
}
int merge(int u,int v){
if (!u) return v;
if (!v) return u;
int t = ++cnt;
sum[t] = sum[u] + sum[v];
tot += 1ll * sum[rs[u]] * sum[ls[v]];
ls[t] = merge(ls[u],ls[v]);
rs[t] = merge(rs[u],rs[v]);
return t;
}
int dfs(){
int x = read(); int u = ++N;
if (x){
modify(rt[u],rt[u],1,n,x);
return u;
}
int L = dfs(),R = dfs(); LL S = 1ll * sum[rt[L]] * sum[rt[R]];
tot = 0;
rt[u] = merge(rt[L],rt[R]);
ans += min(tot,S - tot);
return u;
}
int main(){
n = read();
dfs();
printf("%lld\n",ans);
return 0;
}