• http://main.edu.pl/en/archive/oi/18/rot
题目大意:
个数字出现且仅出现一次
• 允许任意次交换某两棵兄弟子树
• 对交换完毕的树求先序遍历,形成1~n的一个排列
• 求这个排列最小的逆序对个数
• 1 ≤ n ≤ 2 * 1e5 (1e6)
思路:
T (不是叶子)的逆序对由三部分组成
• 1. 左子树的逆序对个数
• 2. 右子树的逆序对个数
• 3. 集合 {(a, b) | a > b, a属于左子树, b属于右子树} 的大小
• 1 & 2可以递归处理变为3
• 于是,自底向上,对每个子树的根判断交换左右子树是否会更优
平衡树 (Splay) 维护子树内数字的有序排列
• 自底向上对Splay进行启发式合并, 合并时计算出3
• O(n log^2(n) )
• 复杂度仍然不足以AC (1e6)
线段树的合并
• 前提,两个线段树的范围相同
• merge(a, b):
• 如果a, b中有一个为空,返回另一个
• 如果a, b都是叶子节点,merge_leaf(a, b)
• 返回merge(a->l, b->l)与merge(a->r, b->r)连接形成的树的根
• 动态开点
• Example
关于时间复杂度
• 整个过程的代价≤将n棵只有单个节点的线段树合并成同一棵树代
价
• 复杂度为O (n logn)
回到此题,可以用一些统计区间内数字个数 (cnt) 的线段树的合并
来解决
• 在merge的过程中统计交换与不交换产生的逆序对数
• a属于T的左子树,b属于T的右子树,ans0为不交换产生的逆序对
数,ans1为交换产生的逆序对数
• merge(a, b):
• 如果a, b中有一个为空,就返回另一个
• ans0 += cnt(a -> r) * cnt(b -> l)
• ans1 += cnt(a -> l) * cnt(b -> r)
• 返回merge(a->l, b->l)与merge(a->r, b->r)连接形成的树的根
• “如果a, b都是叶子节点,merge_leaf(a, b)” ?
• 不存在两个相同元素,a, b不可能同为叶子节点
• 时间复杂度O(n logn)
• 空间复杂度O(n)
• MLE?
• 注意回收内存
• 先递归较大的子树
#include<iostream>
#include<cstdio>
#define ll long long
using namespace std;
inline int read()
{
int x=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
return x*f;
}
int n,sz,seg;
ll ans,cnt1,cnt2;
int v[400005],l[400005],r[400005],root[400005];
int sum[4000005],ls[4000005],rs[4000005];
void readtree(int x)
{
v[x]=read();
if(!v[x])
{
l[x]=++sz;
readtree(l[x]);
r[x]=++sz;
readtree(r[x]);
}
}
void pushup(int k)
{
sum[k]=sum[ls[k]]+sum[rs[k]];
}
void build(int &k,int l,int r,int val)
{
if(!k)k=++seg;
if(l==r){sum[k]=1;return;}
int mid=(l+r)>>1;
if(val<=mid)build(ls[k],l,mid,val);
else build(rs[k],mid+1,r,val);
pushup(k);
}
int merge(int x,int y)
{
if(!x)return y;
if(!y)return x;
cnt1+=(ll)sum[rs[x]]*sum[ls[y]];
cnt2+=(ll)sum[ls[x]]*sum[rs[y]];
ls[x]=merge(ls[x],ls[y]);
rs[x]=merge(rs[x],rs[y]);
pushup(x);
return x;
}
void solve(int x)
{
if(!x)return;
solve(l[x]);solve(r[x]);
if(!v[x])
{
cnt1=cnt2=0;
root[x]=merge(root[l[x]],root[r[x]]);
ans+=min(cnt1,cnt2);
}
}
int main()
{
n=read();++sz;
readtree(1);
for(int i=1;i<=sz;i++)
if(v[i])build(root[i],1,n,v[i]);
solve(1);
printf("%lld",ans);
return 0;
}