其他分享
首页 > 其他分享> > 【Coel.学习笔记】莫队(下)- 树上莫队和二次离线莫队

【Coel.学习笔记】莫队(下)- 树上莫队和二次离线莫队

作者:互联网

树上莫队和二次离线莫队都比较难,所以只讲几个模板(明明是你太懒了)

树上莫队

我们之前处理的问题都是在数列上的,如果换成树,怎么办呢?下面这题给出了一个常用的方法。

SP10707 COT2 - Count on a tree II

洛谷传送门
给定一棵点带权的树,静态询问每两个节点之间(包括端点)路径上的不同权值数。

解析:这道题看起来和上一篇的普通莫队题很像,只不过从序列变成了树。

对于树上问题,有一种通法把它变成序列问题:构造一个欧拉序列。在深度优先遍历的时候,对于每个点到达时和离开时都记录下结果,这时每个节点都会出现两次。我们开设两个数组 \(fir_u\) 和 \(lst_u\),分别记录下 \(u\) 第一次和第二次出现的位置。对于端点 \(x,y\),如果 \(fir_x<fir_y\),判断 \(x,y\) 的最近公共祖先,若为 \(x\),则路径对应到欧拉序列上的区间为 \([fir_x,fir_y]\) 中只出现一次的数字,若不为 \(x\) 则路径对应到欧拉序列上的区间为 \([lst_x,fir_y]\) 中只出现一次的数字,以及 \(x,y\) 的最近公共祖先。经过这样麻烦至极的操作,我们把树上问题变成了序列问题。

但是这么做直接套莫队还是很麻烦,我们要对维护的数组微调一下。维护一个数组 \(vis_u\) 表示数字 \(u\) 在当前区间内出现次数,\(0\) 表示两次 \(1\) 表示一次。给莫队插入时,我们利用异或操作,给 \(vis_u\) 异或上 \(1\)。若异或后值为 \(0\),则相当于普通莫队的删除,反之为插入。显然插入一个数和删除一个数在这个定义下是等价的,所以插入和删除直接利用同一个函数即可。现在插入和删除均为 \(O(1)\),可以直接套莫队解决了。另外本题权值的值域很大,需要做离散化。

虽然看起来很模板,但使用到的算法相当多(离散化,欧拉序列,倍增求 LCA,莫队),还是比较难写的。当本题要求强制在线的时候,有一种名为“树分块”的扩展解法,在此不做详述,感兴趣的读者可以自行查阅资料。

#include <algorithm>
#include <cmath>
#include <cstring>
#include <iostream>
#include <queue>
#include <vector>

#define get(x) (x) / len

using namespace std;

const int maxn = 1e5 + 10;

int n, m, len, a[maxn];
int head[maxn], nxt[maxn], to[maxn], tot;
int dep[maxn], anc[maxn][20];
int euler[maxn], fir[maxn], lst[maxn], idx;
int cnt[maxn], ans[maxn];
bool vis[maxn];
vector<int> rec;

struct node {
    int l, r, id, p;
    node(int id = 0, int l = 0, int r = 0, int p = 0)
        : l(l), r(r), id(id), p(p) {}
    inline bool operator<(const node &x) const {
        int i = get(l), j = get(x.l);
        if (i != j) return i < j;
        return r < x.r;
    }
} q[maxn];

void add(int u, int v) { nxt[tot] = head[u], to[tot] = v, head[u] = tot++; }

inline void init_hash() {
    sort(rec.begin(), rec.end());
    rec.erase(unique(rec.begin(), rec.end()), rec.end());
    for (int i = 1; i <= n; i++)
        a[i] = lower_bound(rec.begin(), rec.end(), a[i]) - rec.begin();
}

void dfs_euler(int u, int fa) {
    euler[++idx] = u;
    fir[u] = idx;
    for (int i = head[u]; ~i; i = nxt[i]) {
        int v = to[i];
        if (v != fa) dfs_euler(v, u);
    }
    euler[++idx] = u;
    lst[u] = idx;
}

// void dfs_lca(int u, int fa) {
//     for (int i = head[u]; ~i; i = nxt[i]) {
//         int v = to[i];
//         if (v == fa) continue;
//         dep[v] = dep[u] + 1;
//         anc[v][0] = u;
//         dfs_lca(v, u);
//     }
//     if (u == 1 && fa == 0)
//         for (int j = 1; j <= 15; j++)
//             for (int i = 1; i <= n; i++) anc[i][j] = anc[anc[i][j - 1]][j -
//             1];
// }

void bfs_lca() {
    queue<int> Q;
    memset(dep, 0x3f, sizeof dep);
    dep[0] = 0, dep[1] = 1;
    Q.push(1);
    while (!Q.empty()) {
        int u = Q.front();
        Q.pop();
        for (int i = head[u]; ~i; i = nxt[i]) {
            int v = to[i];
            if (dep[v] > dep[u] + 1) {
                dep[v] = dep[u] + 1;
                anc[v][0] = u;
                for (int k = 1; k <= 15; k++)
                    anc[v][k] = anc[anc[v][k - 1]][k - 1];
                Q.push(v);
            }
        }
    }
}

int lca(int u, int v) {
    if (dep[u] < dep[v]) swap(u, v);
    for (int i = 15; i >= 0; i--)
        if (dep[anc[u][i]] >= dep[v]) u = anc[u][i];
    if (u == v) return u;
    for (int i = 15; i >= 0; i--)
        if (anc[u][i] != anc[v][i]) u = anc[u][i], v = anc[v][i];
    return anc[u][0];
}

void modify(int x, int &res) {
    vis[x] ^= 1;
    if (vis[x] == 0) {
        cnt[a[x]]--;
        if (!cnt[a[x]]) res--;
    } else {
        if (!cnt[a[x]]) res++;
        cnt[a[x]]++;
    }
}

int main(void) {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    cin >> n >> m;
    for (int i = 1; i <= n; i++) cin >> a[i], rec.push_back(a[i]);
    memset(head, -1, sizeof(head));
    init_hash();
    for (int i = 1, u, v; i <= n - 1; i++) {
        cin >> u >> v;
        add(u, v), add(v, u);
    }
    dfs_euler(1, -1);
    bfs_lca();
    for (int i = 0; i < m; i++) {
        int u, v, l;
        cin >> u >> v;
        if (fir[u] > fir[v]) swap(u, v);
        l = lca(u, v);
        if (u == l)
            q[i] = node(i, fir[u], fir[v], 0);
        else
            q[i] = node(i, lst[u], fir[v], l);
    }
    len = sqrt(idx);
    sort(q, q + m);
    for (int i = 0, L = 1, R = 0, res = 0; i < m; i++) {
        int id = q[i].id, l = q[i].l, r = q[i].r, p = q[i].p;
        while (R < r) modify(euler[++R], res);
        while (R > r) modify(euler[R--], res);
        while (L < l) modify(euler[L++], res);
        while (L > l) modify(euler[--L], res);
        if (p) modify(p, res);
        ans[id] = res;
        if (p) modify(p, res);
    }
    for (int i = 0; i < m; i++) cout << ans[i] << '\n';
    return 0;
}

标签:anc,int,res,离线,Coel,dep,maxn,莫队
来源: https://www.cnblogs.com/Coel-Flannette/p/16537377.html