首页 > 其他分享 >树上莫队 学习笔记

树上莫队 学习笔记

时间:2022-10-16 23:55:24浏览次数:70  
标签:int top 笔记 40005 lca 树上 莫队 euler first

树上莫队本质上是把树上的结点转化为区间信息,从而使用莫队求解。但是不能直接使用树链剖分的 \(\text{dfs}\) 序,因为树上任意一条路径所对应的区间不是连续的。此处需要用到欧拉序。欧拉序即为一个结点入队时将其加到序列里,出队时再加入一次(所以序列的总长度是结点数 \(\times 2\),每个结点恰好出现 \(2\) 次)。

如:

图1

以 \(1\) 为根节点,该树的欧拉序为 \((1, 3, 5, 5, 4, 4, 3, 2, 2, 1)\)。

记 \(\text{first[u]}\) 为 \(u\) 在欧拉序里第一次出现的位置,\(\text{last[u]}\) 为第二次出现的位置,此时如果我们要查询 \((u, v)\) 之间的路径(假设 \(\text{first[u]} \leq \text{first[v]}\)),若 \(u\) 已是 \(v\) 的祖先,则欧拉序的区间 \([\text{first[u]}, \text{first[v]}]\) 中所有出现次数等于 \(1\) 的结点即为 \((u, v)\) 路径上的点;若 \(u\) 不是 \(v\) 的祖先,则 \([\text{last[u]}, \text{first[v]}]\) 中所有出现次数等于 \(1\) 的结点再加上 \(\operatorname{lca}(u, v)\) 即为 \((u, v)\) 之间的路径。

上述预处理可以直接在树剖的过程中完成。通过欧拉序可以直接将路径转化为连续的区间。然后便可以方便地用莫队求解。

例题: SP10707

板子题,看代码即可。

#include <bits/stdc++.h>

using namespace std;

int g[80005];

struct Q {
    int l, r, id, lca;

    const bool operator<(const Q &rhs) const {
        if (g[l] != g[rhs.l])
            return g[l] < g[rhs.l];
        else
            return (g[l] & 1? r > rhs.r: r < rhs.r);
    }
};

int n, m, u, v, nowAns, cnt, l = 1, r, tot, lca, len;
int euler[80005], head[40005], nxt[80005], to[80005], w[40005], ans[100005], dep[40005], first[40005], last[40005], hson[40005], fa[40005], size[40005], cntc[40005], cntid[40005], top[40005];
unordered_map<int, int> cc;
Q q[100005];
char ch;

void read(int &num) {
    while (!isdigit(ch = getchar()));
    
    num = ch - '0';

    while (isdigit(ch = getchar()))
        num = (num << 3) + (num << 1) + ch - '0';
}

void write(int num) {
    if (num >= 10) write(num / 10);

    putchar(num % 10 + '0');
}

void dfs1(int g, int _dep) {
    dep[g] = _dep;
    size[g] = 1;

    for (int i = head[g]; i; i = nxt[i])
        if (to[i] != fa[g]) {
            fa[to[i]] = g;
            dfs1(to[i], _dep + 1);
            size[g] += size[to[i]];

            if (size[to[i]] > size[hson[g]])
                hson[g] = to[i];
        }
}

void dfs2(int g, int _top) {
    euler[++tot] = g;
    first[g] = tot;
    top[g] = _top;

    if (hson[g]) {
        dfs2(hson[g], _top);

        for (int i = head[g]; i; i = nxt[i])
            if (to[i] !=  fa[g] && to[i] != hson[g])
                dfs2(to[i], to[i]);
    }

    euler[++tot] = g;
    last[g] = tot;
}

int LCA(int u, int v) {
    while (top[u] != top[v]) {
        if (dep[top[u]] < dep[top[v]])
            swap(u, v);
        
        u = fa[top[u]];
    }

    return dep[u] < dep[v]? u: v;
}

void move(int id, bool g) {
    if (g) {
        ++cntc[w[id]];

        if (!(cntc[w[id]] ^ 1))
            ++nowAns;
    } else {
        --cntc[w[id]];

        if (!cntc[w[id]])
            --nowAns;
    }
}

int main() {
    read(n), read(m);
    len = (n << 1) / sqrt(m * 2 / 3);

    for (int i = 1; i <= n; ++i) {
        read(w[i]);
        w[i] = (cc[w[i]]? cc[w[i]]: (cc[w[i]] = ++tot));
    }

    for (int i = 1; i <= (n << 1); ++i)
        g[i] = (i - 1) / len;

    for (int i = 0; i < n - 1; ++i) {
        read(u), read(v);
        nxt[++cnt] = head[u];
        head[u] = cnt;
        to[cnt] = v;
        nxt[++cnt] = head[v];
        head[v] = cnt;
        to[cnt] = u;
    }

    tot = 0;
    dfs1(1, 1);
    dfs2(1, 1);

    for (int i = 1; i <= m; ++i) {
        read(u), read(v);
        q[i].id = i;
        lca = LCA(u, v);

        if (first[u] > first[v]) swap(u, v);

        if (lca == u) q[i].l = first[u];
        else q[i].l = last[u], q[i].lca = lca;

        q[i].r = first[v];
    }

    sort(q + 1, q + m + 1);

    for (int i = 1; i <= m; ++i) {
        while (l > q[i].l) {
            ++cntid[euler[--l]];
            move(euler[l], 2 - cntid[euler[l]]);
        }

        while (r < q[i].r) {
            ++cntid[euler[++r]];
            move(euler[r], 2 - cntid[euler[r]]);
        }

        while (r > q[i].r) {
            --cntid[euler[r]];
            move(euler[r], cntid[euler[r]]);
            --r;
        }

        while (l < q[i].l) {
            --cntid[euler[l]];
            move(euler[l], cntid[euler[l]]);
            ++l;
        }

        if (q[i].lca) move(q[i].lca, 1);

        ans[q[i].id] = nowAns;

        if (q[i].lca) move(q[i].lca, 0);
    }

    for (int i = 1; i <= m; ++i)
        write(ans[i]), putchar('\n');

    return 0;
}

标签:int,top,笔记,40005,lca,树上,莫队,euler,first
From: https://www.cnblogs.com/wf715/p/mo-algo-on-tree.html

相关文章

  • oracle学习笔记
    select*fromtest_all;--全量的数据insertintotest_all(ID,NAME,FISRT_FLG)values('1','aaa','1');insertintotest_all(ID,NAME,FISRT_FLG)values......
  • 自适应辛普森法 学习笔记
    对于一个二次函数\(f(x)=ax^2+bx+c\),积分得\(F(x)=\displaystyle\int_0^xf(t)\,\mathrm{d}t=\dfrac{a}{3}x^3+\dfrac{b}{2}x^2+cx+C\)。于是\[\dis......
  • acm训练笔记
    2022.10.16模拟赛2019ChinaCollegiateProgrammingContestFinal(CCPC-Final2019)L题意:只能直行或者右转,问有多少种走遍一个矩阵的方法。解法:构造题,考虑任意一种......
  • Flask学习笔记(十三)-Flask_WTF实现表单
    一、Web表单Web表单是Web应用程序的基本功能。它是HTML页面中负责数据采集的部件。表单有三个部分组成:表单标签、表单域、表单按钮。表单允许用户输入数据,负责HTML页......
  • SpringCloud学习笔记(三)——Ribbon
    一、restTemplate的使用我们直接通过实例来说明和理解。首先新建一个子模块,用来测试restTemplate的使用  在测试的主类中添加如下代码,我们就能够获取百度界面的htm......
  • 系统分析师学习笔记(8)-图论与图示网络的最大流量
    要找出图示的最大流量:1.找出最大运量的路径,该路径的最小值为瓶颈值,抽取该值;2.在找出的路径减去抽取值,为0的路径取消;3.在剩余的路径中,找出最大的抽取值,重复步骤1&2;4.将各个步......
  • 20201302姬正坤Linux第四章学习笔记
    第四章并发编程一、并行计算导论1、顺序算法与并行算法在描述顺序算法中,常用一个begin-end代码块列出算法。该代码块中的所有步骤都是通过某个任务依次执行的。而并行......
  • Java核心技术阅读笔记(第五章)
    Chapter5继承作者:Denis版本:1.0编写时间:2022/10/16编写地点:中国山西省5.1类、超类和子类如果一个类继承自另一个类,那么这个类被称为子类,被继承的类被称为超类......
  • 第四单元读书笔记
    第四章并发编程介绍Pthread中的线程操作,包括线程管理函数,互斥量、连接、条件变量和屏障等线程同步工具。4.1并行计算导论4.1.1顺序算法与并序算法使用cobegin-c......
  • 数据库学习笔记04- redis
    5,Redis基础redis--KV数据库--内存--单线程+异步i/o(多路io复用)计算密集型应用:多进程+多进程IO密集型应用:单线程+异步IO(协程)2008年--redis--》REmote......