[BZOJ3123][Sdoi2013]森林(主席树+启发式合并)

首先介绍下树上主席树:
在序列上的主席树,是对序列的每一个前缀都建立一个线段树版本。
而树上主席树,是对树上的每一个节点到根的路径都建立一个线段树版本。
而如果 query(u) 是查询节点 u 到根的路径上的信息,则查询u v 的路径上的信息,就是:
query(u)+query(v)query(lca(u,v))query(fa[lca(u,v)])
这样,就可以在权值线段树上二分,查询路径第 k 小值了。
而最后的问题就是加边。可以考虑启发式合并,也就是把节点数少的树合并到节点数多的树上面去,这样每个节点被合并的次数不超过O(logn)
在合并之后,也必须初始化节点数少的树的倍增LCA数组,以及节点数少的树的线段树版本。所以,一次合并的操作为 O(log2n)
代码:

#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
inline int read() {
    int res = 0; bool bo = 0; char c;
    while (((c = getchar()) < '0' || c > '9') && c != '-');
    if (c == '-') bo = 1; else res = c - 48;
    while ((c = getchar()) >= '0' && c <= '9')
        res = (res << 3) + (res << 1) + (c - 48);
    return bo ? ~res + 1 : res;
}
inline char get() {
    char c; while ((c = getchar()) != 'L' && c != 'Q');
    return c;
}
const int N = 7e5 + 5, Orz = 2e7 + 5, LogN = 20;
struct cyx {
    int lc, rc, cnt;
} T[Orz];
int n, m, QAQ, rt[N], sze[N], ecnt, nxt[N], adj[N], go[N], fa[N][LogN],
val[N], RT[N], dep[N], lst;
bool vis[N];
void add_edge(int u, int v) {
    nxt[++ecnt] = adj[u]; adj[u] = ecnt; go[ecnt] = v;
    nxt[++ecnt] = adj[v]; adj[v] = ecnt; go[ecnt] = u;
}
void build(int y, int &x, int l, int r, int p) {
    T[x = ++QAQ] = T[y]; T[x].cnt++;
    int mid = l + r >> 1; if (l == r) return;
    if (p <= mid) build(T[y].lc, T[x].lc, l, mid, p);
    else build(T[y].rc, T[x].rc, mid + 1, r, p);
}
void dfs(int u, int fu, int r) {
    int i; build(rt[fu], rt[u], 0, 1e9, val[u]); sze[u] = 1; RT[u] = r;
    dep[u] = dep[fa[u][0] = fu] + 1;
    for (i = 0; i <= 17; i++) fa[u][i + 1] = fa[fa[u][i]][i];
    for (int e = adj[u], v; e; e = nxt[e]) {
        if ((v = go[e]) == fu) continue;
        dfs(v, u, r); sze[u] += sze[v];
    }
    vis[u] = 1;
}
void link(int x, int y) {
    if (sze[RT[x]] < sze[RT[y]]) swap(x, y);
    sze[RT[x]] += sze[RT[y]]; dfs(y, x, RT[x]);
    add_edge(x, y);
}
int lca(int u, int v) {
    int i; if (dep[u] < dep[v]) swap(u, v);
    for (i = 18; i >= 0; i--) {
        if (dep[fa[u][i]] >= dep[v]) u = fa[u][i];
        if (u == v) return u;
    }
    for (i = 18; i >= 0; i--)
        if (fa[u][i] != fa[v][i]) u = fa[u][i], v = fa[v][i];
    return fa[u][0];
}
int query(int p1, int p2, int p3, int p4, int l, int r, int k) {
    if (l == r) return l; int mid = l + r >> 1;
    int delta = T[T[p1].lc].cnt + T[T[p2].lc].cnt
        - T[T[p3].lc].cnt - T[T[p4].lc].cnt;
    if (delta >= k)
        return query(T[p1].lc, T[p2].lc, T[p3].lc, T[p4].lc, l, mid, k);
    else
        return query(T[p1].rc, T[p2].rc, T[p3].rc, T[p4].rc, mid + 1, r, k - delta);
}
int ask(int x, int y, int k) {
    int z = lca(x, y);
    return query(rt[x], rt[y], rt[z], rt[fa[z][0]], 0, 1e9, k);
}
int main() {
    int i, x, y, k; read(); n = read(); m = read(); int Q = read();
    for (i = 1; i <= n; i++) val[i] = read();
    for (i = 1; i <= m; i++) x = read(), y = read(), add_edge(x, y);
    for (i = 1; i <= n; i++) if (!vis[i]) dfs(i, 0, i);
    while (Q--) {
        char op = get(); x = read() ^ lst; y = read() ^ lst;
        if (op == 'L') link(x, y);
        else k = read() ^ lst, printf("%d\n", lst = ask(x, y, k));
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值