Description
现在有一棵二叉树,所有非叶子节点都有两个孩子。在每个叶子节点上有一个权值(有 (n) 个叶子节点,满足这些权值为 (1cdots n) 的一个排列)。
可以任意交换每个非叶子节点的左右孩子。
要求进行一系列交换,使得最终所有叶子节点的权值按照遍历序写出来,逆序对个数最少。
Hint
(1le nle 2 imes 10^5)
Solution
可以发现,对于其中一颗子树,不论如何变换都不会改变 这一颗子树中的结点与其他位置的结点组合产生的逆序对的个数 即这颗子树的变换不会改变上层之间的逆序对,所以只要考虑这颗子树中的就行了。
首先对所有结点开一个权值线段树,之后逐层合并即可。现在的问题就是,左右子树如何放置(是否交换)产生的逆序对最小。
我们可以在线段树合并的时候干这个事情。具体地,就是说当合并到结点 (a) (左子树结点) (, b)(右子树结点) 时,记 不交换产生的逆序对为 (u) ,交换的为 (v),那么:
- 将 (u) 加上 (a) 的右子树的大小 ( imes b) 左子树的大小
- 将 (v) 加上 (a) 的左子树的大小 ( imes b) 右子树的大小
一次合并完之后,( ext{ans} leftarrow ext{ans} + min(u, v)) 即可。时空复杂度 (O(nlog n))
注意,实际节点数可能有 (n imes 2) 这么多,应开大空间。
Code
/*
* Author : _Wallace_
* Source : https://www.cnblogs.com/-Wallace-/
* Problem : LOJ #2163 POI2011 Tree Rotations
*/
#include <iostream>
using namespace std;
const int N = 4e5 + 5;
const int S = N << 6;
int lc[S], rc[S], total = 0;
long long sum[S];
#define mid ((l + r) >> 1)
void increase(int &rt, int l, int r, int p) {
if (!rt) rt = ++total;
sum[rt]++;
if (l == r) return;
if (p <= mid) increase(lc[rt], l, mid, p);
else increase(rc[rt], mid + 1, r, p);
}
int merge(int a, int b, int l, int r, long long& u, long long& v) {
if (!a || !b) return a | b;
if (l == r) return sum[a] += sum[b], a;
u += sum[rc[a]] * sum[lc[b]];
v += sum[lc[a]] * sum[rc[b]];
lc[a] = merge(lc[a], lc[b], l, mid, u, v);
rc[a] = merge(rc[a], rc[b], mid + 1, r, u, v);
return sum[a] = sum[lc[a]] + sum[rc[a]], a;
}
#undef mid
int n;
long long ans = 0ll;
int solve() {
int rt = 0, cur;
cin >> cur;
if (!cur) {
int l = solve(), r = solve();
long long u = 0ll, v = 0ll;
rt = merge(l, r, 1, n, u, v);
ans += min(u, v);
} else increase(rt, 1, n, cur);
return rt;
}
signed main() {
cin >> n;
solve();
cout << ans << endl;
return 0;
}