[SDOI2013] 森林【可持久化线段树+启发式合并+LCA】

传送门

吐槽:题目上给的 testcase 太迷惑人了。。。

题目描述:

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

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

  1. Q x y k查询点x到点y路径上所有的权值中,第k小的权值是多少。此操作保证点x和点y连通,同时这两个节点的路径上至少有k个点。
  2. L x y在点x和点y之间连接一条边。保证完成此操作后,仍然是一片森林。

为了体现程序的在线性,我们把输入数据进行了加密。设lastans为程序上一次输出的结果,初始的时候lastans为0。

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

请写一个程序來帮助小Z完成这些操作。

对于所有的数据,n,m,T<=8*10^4.

求解树上一条链的第K小模板题博客:Count on a tree
解题思路 :
题目上要求强制在线,所以不能离线建树。我们需要在线维护树的信息。如果要我们合并两个棵树,我们可以将结点数量小的那棵树接在另一颗树上(为了保证复杂度)。我们对小的那棵树进行DFS,在DFS过程中进行修改ST表和维护线段树。思路实现了,剩下就是代码了。(注意细节)

代码:

#include<bits/stdc++.h>
using namespace std;
const int maxn = 8e4 + 5;

inline void read(int &num) {
    int x = 0, f = 1;
    char ch = ' ';
    while (ch != '-' && (ch < '0' || ch > '9'))ch = getchar();
    if (ch == '-')f = -1, ch = getchar();
    while (ch >= '0' && ch <= '9')x = x * 10 + ch - '0', ch = getchar();
    num = x * f;
}

struct node {
    int ls, rs, cnt;
#define left(a, b) p[a].ls, p[b].ls, l, mid
#define right(a, b) p[a].rs, p[b].rs, mid + 1, r
} p[maxn * 1000];
int root[maxn], times;

struct Edge {
    int e;
    int p;
} load[maxn * 2];
int head[maxn], sign;

void add_edge(int s, int e) {
    load[++sign] = Edge{e, head[s]};
    head[s] = sign;
}

int value[maxn], grand[maxn][20], depth[maxn], N;
int fa[maxn], sizes[maxn];
///并查集维护数量
int find(int x) { return fa[x] == x ? x : fa[x] = find(fa[x]); }

void insert(int &now, int old, int l, int r, int x) {
    now = ++times;
    p[now] = p[old], p[now].cnt++;
    if (l == r) return;
    int mid = (l + r) >> 1;
    if (x <= mid) insert(left(now, old), x);
    else insert(right(now, old), x);
}

int query(int s, int e, int fa, int ga, int l, int r, int k) {
    if (l == r) return l;
    int x = p[p[s].ls].cnt + p[p[e].ls].cnt - p[p[fa].ls].cnt - p[p[ga].ls].cnt;
    int mid = (l + r) >> 1;
    if (k <= x) return query(p[s].ls, p[e].ls, p[fa].ls, p[ga].ls, l, mid, k);
    else return query(p[s].rs, p[e].rs, p[fa].rs, p[ga].rs, mid + 1, r, k - x);
}
///重新构建,并且维护LCA和状态
void dfs(int s, int pre) {
    grand[s][0] = pre;
    for (int i = 1; i <= N; i++) grand[s][i] = grand[grand[s][i - 1]][i - 1];
    depth[s] = depth[pre] + 1;
    insert(root[s], root[pre], 1, (int) 1e9, value[s]);
    for (int i = head[s], e; ~i; i = load[i].p) {
        e = load[i].e;
        if (e ^ pre) dfs(e, s);
    }
}

int get_lca(int a, int b) {
    if (depth[a] > depth[b]) swap(a, b);
    for (int i = N; i >= 0; i--) if (depth[b] >= depth[a] && depth[grand[b][i]] >= depth[a]) b = grand[b][i];
    for (int i = N; i >= 0; i--) if (grand[a][i] != grand[b][i]) { a = grand[a][i], b = grand[b][i]; }
    return a == b ? a : grand[b][0];
}

void merge(int s, int e) {
    ///将结点少的树合并在结点多的树上
    int fs = find(s), fe = find(e);
    if (sizes[fs] > sizes[fe]) {
        sizes[fs] += sizes[fe];
        fa[fe] = fs;
        dfs(e, s);
    } else {
        sizes[fe] += sizes[fs];
        fa[fs] = fe;
        dfs(s, e);
    }
}

void init(int n) {
    N = log2(n);
    for (int i = 1; i <= n; i++) {
        fa[i] = i, sizes[i] = 1;
        head[i] = -1;
    }
    depth[0] = -1;
}

int main() {
    char mark[3];
    int testcase, n, m, op, s, e, k, lastans = 0;
    read(testcase), read(n), read(m), read(op);
    init(n);
    for (int i = 1; i <= n; i++) {
        read(value[i]);
        ///每个点状态的初始化
        insert(root[i], root[0], 1, 1e9, value[i]);
    }
    for (int i = 1; i <= m; i++) {
        scanf("%d %d", &s, &e);
        add_edge(s, e), add_edge(e, s);
        merge(s, e); ///合并两棵树
    }
    while (op--) {
        scanf("%s", mark);
        if (mark[0] == 'Q') {
            read(s), read(e), read(k);
            s ^= lastans, e ^= lastans, k ^= lastans;
            int lca = get_lca(s, e);
            printf("%d\n", lastans=query(root[s], root[e], root[lca], root[grand[lca][0]], 1, 1e9, k));
        } else {
            read(s), read(e);
            s ^= lastans, e ^= lastans;
            add_edge(s, e), add_edge(e, s);
            merge(s, e);
        }
    }
    return 0;
}

 

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值