BZOJ 3123 LCA + 主席树 + 启发式合并

6 篇文章 0 订阅
5 篇文章 0 订阅

大致题意:有两种操作。
1. 查询节点x到节点y的路径上的第k小。
2. 连接点x和点y。

本题只有一组测试样例,且存在垃圾数据!
需要离散化不必多说。
对于第一种操作,经典的LCA + 主席树问题。每次以父亲节点为上一版本建树,对于一次询问的答案就是在区间 xlca[x,y]+yfa[lca[x,y]] 的答案。
对于第二种操作,每次启发式合并即可。对于新加入的点,需要对这些点进行LCA和主席树的相关信息的更新。
总复杂度 O(nlognlogn)

#include <bits/stdc++.h>
#define all(x) x.begin(), x.end()
using namespace std;

/*
1. 树上路径第k小。
转化成LCA+主席树
2. 点之间连边,启发式合并
3. 点权大,离散化
*/

const int maxn = 88000;
int ans, t, n, m, q;
int val[maxn];
vector<int> G[maxn];
vector<int> V, C;

int getid(int x) {
    return lower_bound(all(V), x) - V.begin() + 1;
}

int getval(int x) {
    return V[x-1];
}

struct seg{ int l, r, sum;} tr[maxn*100];
int idc, root[maxn];

void update(int &x, int y, int l, int r, int pos, int val) {
    x = ++idc; tr[x] = tr[y]; tr[x].sum += val;
    if(l == r) return ;
    int m = (l + r) >> 1;
    if(pos <= m) update(tr[x].l, tr[y].l, l, m, pos, val);
    else update(tr[x].r, tr[y].r, m+1, r, pos, val);
}

int ask(int x, int f1, int y, int f2, int l, int r, int k) {
    if(l == r) return l;
    int m = (l + r) >> 1;
    int sum = tr[tr[x].l].sum - tr[tr[f1].l].sum + tr[tr[y].l].sum - tr[tr[f2].l].sum;
    if(sum >= k) return ask(tr[x].l, tr[f1].l, tr[y].l, tr[f2].l, l, m, k);
    else ask(tr[x].r, tr[f1].r, tr[y].r, tr[f2].r, m+1, r, k-sum);
}

int p[maxn][20], dep[maxn];

void dfs(int u, int fa) {
    C.push_back(u);
    dep[u] = dep[fa] + 1;
    p[u][0] = fa;
    update(root[u], root[fa], 1, V.size(), getid(val[u]), 1);
    for(int i = 0; i < G[u].size(); i++) {
        int v = G[u][i];
        if(v == fa) continue;
        dfs(v, u);
    }
}

int lca_up(int u, int len) {
    for(int i = 0; i < 20; i++) {
        if(u != 0 && len&(1<<i))
            u = p[u][i];
    }
    return u;
}

int lca(int u, int v) {
    if(dep[u] < dep[v]) swap(u, v);
    u = lca_up(u, dep[u] - dep[v]);
    if(v == u) return v;
    for(int i = 19; i >= 0; i--) {
        if(p[u][i] == p[v][i]) continue;
        u = p[u][i]; v = p[v][i];
    }
    return p[u][0];
}

void lca_update() {
    for(int j = 1; j < 20; j++) {
        for(int _ = 0; _ < C.size(); _++) {
            int i = C[_];
            if(p[i][j-1] == 0) p[i][j] = 0;
            else p[i][j] = p[p[i][j-1]][j-1];
        }
    }
    C.clear();
}

int fa[maxn], sz[maxn];

int find(int x) {
    return x == fa[x]? fa[x] : fa[x] = find(fa[x]);
}

void unite(int x, int y) {
    int f1 = find(x), f2 = find(y);
    if(f1 == f2) return ;
    sz[f1] += sz[f2];
    fa[f2] = f1;
}

void init() {
    V.clear(); C.clear();
    for(int i = 0; i < maxn; i++)
        G[i].clear(), fa[i] = i, sz[i] = 1;
    ans = idc = 0;
}

int main() {
    scanf("%d", &t);
    init();
    scanf("%d%d%d", &n, &m, &q);
    for(int i = 1; i <= n; i++)
        scanf("%d", &val[i]), V.push_back(val[i]);
    sort(all(V));
    V.erase(unique(all(V)), V.end());
    for(int i = 1; i <= m; i++)  {
        int u, v;
        scanf("%d%d", &u, &v);
        G[u].push_back(v);
        G[v].push_back(u);
        unite(u, v);
    }
    dep[0] = 0;
    for(int i = 1; i <= n; i++)
        if(!dep[i])
            dfs(i, 0);
    lca_update();
    while(q--) {
        char cmd;
        scanf(" %c", &cmd);
        if(cmd == 'Q') {
            int x, y, k;
            scanf("%d%d%d", &x, &y, &k);
            x ^= ans, y ^= ans, k ^= ans;
            int lc = lca(x, y);
            printf("%d\n", ans = getval(ask(root[x], root[lc], root[y], root[p[lc][0]], 1, V.size(), k)));
        } else {
            int x, y;
            scanf("%d%d", &x, &y);
            x ^= ans, y ^= ans;
            int f1 = find(x), f2 = find(y);
            if(sz[f1] < sz[f2]) swap(x, y);
            unite(x, y);
            G[x].push_back(y); G[y].push_back(x);
            dfs(y, x);
            lca_update();
        }
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值