标签:math 节点 esc 最小 main 权值线段树 注意 long merge
现在有一棵二叉树,所有非叶子节点都有两个孩子。在每个叶子节点上有一个权值(有 \(n\) 个叶子节点,满足这些权值为 \(1\cdots n\) 的一个排列)。
可以任意交换每个非叶子节点的左右孩子。
要求进行一系列交换,使得最终所有叶子节点的权值按照遍历序写出来,逆序对个数最少。
\(1\le n\le 2\times 10^5\)
可以发现,对于其中一颗子树,不论如何变换都不会改变 这一颗子树中的结点与其他位置的结点组合产生的逆序对的个数 即这颗子树的变换不会改变上层之间的逆序对,所以只要考虑这颗子树中的就行了。
首先对所有结点开一个权值线段树,之后逐层合并即可。现在的问题就是,左右子树如何放置(是否交换)产生的逆序对最小。
我们可以在线段树合并的时候干这个事情。具体地,就是说当合并到结点 \(a\) (左子树结点) \(, b\)(右子树结点) 时,记 不交换产生的逆序对为 \(u\) ,交换的为 \(v\),那么:
一次合并完之后,\(\text{ans} \leftarrow \text{ans} + \min(u, v)\) 即可。
注意,实际节点数可能有 \(n\times 2\) 这么多,应开大空间。
#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;
}
「Loj #2163」「Poi2011」Tree Rotations
标签:math 节点 esc 最小 main 权值线段树 注意 long merge
原文地址:https://www.cnblogs.com/-Wallace-/p/12723272.html