P3521 题解
非线段树合并做法。复杂度多一只 log
,但是好写。洛谷过得去,loj 过不去。
跳过不重要的部分,直达核心 —— 如何在递归时计算两棵子树互相的贡献?
题解区清一色线段树合并从值域角度考虑。但是显然倍增也能做啊。
考虑倍增时把小的合并到大的上。对每个节点维护一棵平衡树,从下往上合并时,把小树合并进大树中的同时计算答案,然后直接让父亲继承大树。这里我的做法是直接传指针,直接把指向原大树的指针传上去当作该树的指针。这样就做完了,细节看代码。
考虑复杂度。一个节点被从一棵树复制到另一棵树时所在子树大小至少加倍。因此一个节点至多被复制 \(\log n\) 次,单次复杂度 \(O(\log n)\),因此复杂度 \(O(n \log^2 n)\)。
注意到你需要在合并时求出某个平衡树有多少数小于给定数。这个用 set
不行,要上 pbds
。
代码不到 1k
,大部分是 pb_ds
。
这篇题解 的归并做法和我本质一致。但是必须要保证合并答案时复杂度仅与小树有关才能做到 \(O(n \log^2 n)\)。
这篇题解 的 dsu on tree
是我的做法,写得好丑,但跑得好快。树状数组常数小 pbds
太多了。
#include <cstdio>
#include <set>
#include <algorithm>
#include <ext/pb_ds/assoc_container.hpp>
#include <ext/pb_ds/tree_policy.hpp>
#define LL long long
using namespace std;
using namespace __gnu_pbds;
const int M = 5e5 + 5;
tree<int, null_type, less<int>, rb_tree_tag, tree_order_statistics_node_update> s[M];
int cnt;
LL ans;
int build() {
int x; scanf("%d", &x);
int rt = ++cnt;
if(x != 0) return s[rt].insert(x), rt;
else {
int l = build(), r = build();
if(s[l].size() < s[r].size()) swap(l, r);
LL tmp = 0, tot = 1ll * s[l].size() * s[r].size();
for(auto u : s[r]) {
if(s[l].lower_bound(u) == s[l].end()) tmp += s[l].size();
else tmp += 1ll * s[l].order_of_key(*s[l].lower_bound(u));
}
for(auto u : s[r]) s[l].insert(u);
ans += min(tmp, tot - tmp);
return l;
}
}
int main() {
int n; scanf("%d", &n); build();
printf("%lld\n", ans);
}
本文来自博客园,作者:purplevine,转载请注明原文链接:https://www.cnblogs.com/purplevine/p/17040758.html