P3302 [SDOI2013]森林(树上主席树+启发式合并)

这篇博客讨论了一种处理包含节点权值和连边操作的森林数据结构问题。通过使用主席树和路径压缩,实现了在线性时间内查询两点间路径上第k小权值的功能,并能有效处理连边操作,保持森林性质。文章详细介绍了算法实现过程,包括树的秩合并和LCA(最近公共祖先)计算,以及如何处理加密输入数据。
摘要由CSDN通过智能技术生成

小 Z 有一片森林,含有 N 个节点,每个节点上都有一个非负整数作为权值。初始的时候,森林中有 M 条边。

小Z希望执行 T 个操作,操作有两类:

Q x y k 查询点 x 到点 y 路径上所有的权值中,第 k 小的权值是多少。此操作保证点 x 和点 y 连通,同时这两个节点的路径上至少有 k 个点。
L x y 在点 x 和点 y 之间连接一条边。保证完成此操作后,仍然是一片森林。
为了体现程序的在线性,我们把输入数据进行了加密。设 lastans 为程序上一次输出的结果,初始的时候 lastans0

对于一个输入的操作 Q x y k,其真实操作为 Q x^lastans y^lastans k^lastans

对于一个输入的操作L x y,其真实操作为 L x^lastans y^lastans。其中 ^ 运算符表示异或,等价于 Pascal 中的 xor 运算符。

请写一个程序来帮助小 Z 完成这些操作。
思路:
树上节点第k小, 我们很容易想到主席树, 但是操作2却有连边操作, 这时候我们可以考虑按树的秩的大小来合并两棵树, 并重新维护被合并的树上的主席树.
tips: 不同于一段序列上的kth查询, 树上kth查询需要求出两节点的lca, 以及lcafather, 类似于树上点差分.
这题空间得开很大, 因为每次合并两棵树都相当于重构一颗主席树.

//#pragma GCC optimize(2)
//#pragma GCC optimize(3,"Ofast","inline")
#include<bits/stdc++.h>
using namespace::std;
#define fi first
#define se second
#define lc (x << 1)
#define rc (x << 1 | 1)
#define mid ((l + r) >> 1)
#define zty(x) cout << #x << " = " << x << '\n';
const int MAX_N = 8e4 + 5;
const int MAX_M = 1e6 + 5;
const int mod = 998244353;
const int INF = 0x3f3f3f3f;

int test;
int n, m, q, t;
int head[MAX_N], tot;
struct node { int to, next;} G[MAX_M * 2];
int a[MAX_N], b[MAX_N], sum;
int fa[MAX_N][20], siz[MAX_N], dep[MAX_N], root[MAX_N];
inline void add(int u, int v) {
    G[++ tot] = node{v, head[u]};
    head[u] = tot;
}
struct President_Tree {
    int root[MAX_N * 150], tot;
    int cnt[MAX_N * 150], son[MAX_N * 150][2];
    int insert(int pre, int l, int r, int pos) {
        int x = ++ tot;
        cnt[x] = cnt[pre] + 1;
        son[x][0] = son[pre][0];
        son[x][1] = son[pre][1];
        if(l == r) return x;
        if(pos <= mid) son[x][0] = insert(son[pre][0], l, mid, pos);
        else son[x][1] = insert(son[pre][1], mid + 1, r, pos);
        return x;
    }
    int query(int x, int y, int ff, int fff, int l, int r, int k) {
        int lcnt = cnt[son[x][0]] + cnt[son[y][0]] - cnt[son[ff][0]] - cnt[son[fff][0]];
        if(l == r) return b[l];
        if(k <= lcnt) return query(son[x][0], son[y][0], son[ff][0], son[fff][0], l, mid, k);
        else return query(son[x][1], son[y][1], son[ff][1], son[fff][1], mid + 1, r, k - lcnt);
    }
} pt;
bitset< MAX_N > vis;
void dfs(int x, int fat, int boss) {
    vis[x] = true;
    fa[x][0] = fat;
    dep[x] = dep[fat] + 1;
    root[x] = boss;
    ++ siz[boss];
    for(int i = 1; i <= t; ++ i) fa[x][i] = fa[fa[x][i - 1]][i - 1];
    pt.root[x] = pt.insert(pt.root[fat], 1, sum, a[x]);
    for(int i = head[x]; i; i = G[i].next) {
        int y = G[i].to;
        if(y != fat) dfs(y, x, boss);
    }
}
int lca(int x, int y) {
    if(dep[x] > dep[y]) swap(x, y);
    for(int i = t; i >= 0; -- i) {
        if(dep[fa[y][i]] >= dep[x]) y = fa[y][i];
    }
    if(x == y) return x;
    for(int i = t; i >= 0; -- i) {
        if(fa[x][i] != fa[y][i]) {
            x = fa[x][i]; y = fa[y][i];
        }
    }
    return fa[x][0];
}
void solve() {
    cin >> test;
    cin >> n >> m >> q;
    t = (int)(log(n) / log(2)) + 1;
    for(int i = 1; i <= n; ++ i) {
        cin >> a[i];
        root[i] = i;
    }
    memcpy(b, a, sizeof(a));
    sort(b + 1, b + n + 1);
    sum = unique(b + 1, b + n + 1) - (b + 1);
    for(int i = 1; i <= n; ++ i) a[i] = lower_bound(b + 1, b + sum + 1, a[i]) - b;
    while(m -- ) {
        int x, y;
        cin >> x >> y;
        add(x, y); add(y, x);
    }
    for(int i = 1; i <= n; ++ i) {
        if(!vis[i]) {
            root[i] = i;
            dfs(i, 0, i);
        }
    }
    int lastans = 0;
    while(q -- ) {
        string op;
        int x, y, k;
        cin >> op >> x >> y;
        x ^= lastans;
        y ^= lastans;
        if(op == "Q") {
            cin >> k;
            k ^= lastans;
            int ff = lca(x, y);
            int ret = pt.query(pt.root[x], pt.root[y], pt.root[ff], pt.root[fa[ff][0]], 1, sum, k);
            lastans = ret;
            cout << ret << '\n';
        } else {
            add(x, y); add(y, x);
            int rtx = root[x], rty = root[y];
            if(siz[rtx] < siz[rty]) {
                swap(x, y);
                swap(rtx, rty);
            }
            dfs(y, x, rtx);
        }
    }
}
signed main() {
//    freopen("SPO.IN", "r", stdin);
//    freopen("SPO.OUT", "w", stdout);
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    cout.tie(nullptr);
    int ZTY = 1;
    //cin >> ZTY;
    while(ZTY -- ) solve();
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值