ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

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

2022-07-31 16:33:45  阅读:167  来源: 互联网

标签:anc int res 离线 Coel dep maxn 莫队


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

树上莫队

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

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

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有