Loading

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);
}
posted @ 2023-01-10 16:50  purplevine  阅读(38)  评论(0编辑  收藏  举报