Description
现在有一棵二叉树,所有非叶子节点都有两个孩子。在每个叶子节点上有一个权值(有n个叶子节点,满足这些权值为1..n的一个排列)。可以任意交换每个非叶子节点的左右孩子。
要求进行一系列交换,使得最终所有叶子节点的权值按照中序遍历写出来,逆序对个数最少。
对于100%的数据:2<=n<=200000。
Solution
双倍经验!
可以发现交换x的儿子并不会对与x同层的y造成影响,因此我们只需要每一层贪心即可
考虑怎么求顺序对和逆序对。我们对每一个节点开权值线段树,那么合并两个儿子的线段树的过程实际上可以看成是我们对权值进行分治,并分别统计左边对右边的贡献
那么动态开点权值线段树合并就可以了
一开始疯狂RE是因为权值的数组没开够2n��这大概是老年选手的特征
Code
#include <stdio.h>
#include <string.h>
#include <algorithm>
#define rep(i,st,ed) for (int i=st;i<=ed;++i)
typedef long long LL;
const int N=200005;
struct treeNode {
int l,r,size;
} t[N*21],tr[N*2];
int root[N*2],a[N*2],tot,cnt,n;
LL ans,A,B;
int read() {
int x=0,v=1; char ch=getchar();
for (;ch<'0'||ch>'9';v=(ch=='-')?(-1):(v),ch=getchar());
for (;ch<='9'&&ch>='0';x=(x<<1)+(x<<3)+ch-'0',ch=getchar());
return x*v;
}
int merge(int x,int y,int tl,int tr) {
if (!x||!y) return x+y;
if (tl==tr) {
t[x].size+=t[y].size;
return x;
}
int mid=(tl+tr)>>1;
A+=(1LL*t[t[x].l].size*t[t[y].r].size);
B+=(1LL*t[t[x].r].size*t[t[y].l].size);
t[x].l=merge(t[x].l,t[y].l,tl,mid);
t[x].r=merge(t[x].r,t[y].r,mid+1,tr);
t[x].size=t[t[x].l].size+t[t[x].r].size;
return x;
}
void modify(int &now,int tl,int tr,int x) {
if (!now) now=++tot; t[now].size++;
if (tl==tr) return ;
int mid=(tl+tr)>>1;
if (x<=mid) modify(t[now].l,tl,mid,x);
else modify(t[now].r,mid+1,tr,x);
}
int query(int now,int tl,int tr,int l,int r) {
if (!now) return 0;
if (tl==l&&tr==r) return t[now].size;
int mid=(tl+tr)>>1;
if (r<=mid) return query(t[now].l,tl,mid,l,r);
else return query(t[now].l,tl,mid,l,mid)+query(t[now].r,mid+1,tr,mid+1,r);
}
void build(int &now) {
now=++cnt;
int x=read();
if (x) return (void) (a[now]=x);
build(tr[now].l); build(tr[now].r);
}
void solve(int now) {
if (a[now]) {
modify(root[now],1,n,a[now]);
return ;
}
solve(tr[now].l);
solve(tr[now].r);
A=B=0;
root[now]=merge(root[tr[now].l],root[tr[now].r],1,n);
ans+=std:: min(A,B);
}
int main(void) {
n=read(); int gg=0;
build(gg);
solve(gg);
printf("%lld\n", ans);
return 0;
}