您的位置:首页 > 其它

BZOJ 2212 线段树启发式合并

2017-09-19 17:14 399 查看
简略题意:现在有一棵二叉树,所有非叶子节点都有两个孩子。在每个叶子节点上有一个权值(有n个叶子节点,满足这些权值为1..n的一个排列)。可以任意交换每个非叶子节点的左右孩子。要求进行一系列交换,使得最终所有叶子节点的权值按照遍历序写出来,逆序对个数最少。

考虑题中的唯一操作,交换两个孩子。

对每个节点考虑两个孩子对答案的贡献:左孩子的贡献 + 右孩子的贡献 + 左孩子比右孩子大产生的贡献。

交换两个孩子只会使得第三种贡献变化,且只对当前节点有影响。

因此我们只需要考虑如何计算逆序对。

对节点建权值线段树,考虑将左右孩子的线段树合并时,不反转的情况下,每次统计左孩子对右孩子的影响的数量,然后递归合并下去。反转的情况下就是考虑右孩子对左孩子的影响。

其实这里本质上和cdq分治是一样的,可以做到统计贡献的不重不漏。

#include <bits/stdc++.h>
#define all(x) x.begin(), x.end()
using namespace std;

const int maxn = 4400000;
int n;
int cid;

int root[maxn], l[maxn], r[maxn], v[maxn];
struct Seg {
int l, r, sum;
} tr[maxn];

void pushup(int x) {
tr[x].sum = tr[tr[x].l].sum + tr[tr[x].r].sum;
}

int update(int pos, int l, int r) {
int x = ++cid;
int m = l + r >> 1;
if(l == r) {
tr[x].sum = 1;
return x;
}
if(pos <= m)
tr[x].l = update(pos, l, m);
else
tr[x].r = update(pos, m+1, r);
pushup(x);
return x;
}

int sz = 1;

void read(int x) {
scanf("%d", &v[x]);
if(v[x] == 0) {
l[x] = ++sz;
read(l[x]);
r[x] = ++sz;
read(r[x]);
} else {
root[x] = update(v[x], 1, n);
}
}

long long ans = 0, s1 = 0, s2 = 0;

int mergetree(int x, int y, int tp) {
if(!x) return y;
if(!y) return x;
if(tp == 1) {
s1 += 1LL * tr[tr[x].r].sum * tr[tr[y].l].sum;
s2 += 1LL * tr[tr[x].l].sum * tr[tr[y].r].sum;
} else {
s2 += 1LL * tr[tr[x].r].sum * tr[tr[y].l].sum;
s1 += 1LL * tr[tr[x].l].sum * tr[tr[y].r].sum;
}
tr[x].l = mergetree(tr[x].l, tr[y].l, tp);
tr[x].r = mergetree(tr[x].r, tr[y].r, tp);
pushup(x);
return x;
}

void dfs(int x) {
if(!x)
return ;
dfs(l[x]), dfs(r[x]);
if(!v[x]) {
s1 = s2 = 0;
if(tr[root[l[x]]].sum > tr[root[r[x]]].sum)
root[x] = mergetree(root[l[x]], root[r[x]], 1);
else
root[x] = mergetree(root[r[x]], root[l[x]], 2);
ans += min(s1, s2);
}
}

void init() {
cid = 0;
}

int main() {
init();
scanf("%d", &n);
read(1);
dfs(1);
printf("%lld\n", ans);
return 0;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: