AcWing 392. 会合

2021-03-20 05:23

阅读:663

标签:简化   公共祖先   swa   math   ace   main   不难   如何   lin   

一个思路不难,但是实现起来有点毒瘤的题。

显然题目给出的是基环树(内向树)森林。

把每一个基环抠出来。

大力分类讨论:

  1. \(a, b\) 不在一个联通量里,显然是 \(-1, -1\)
  2. \(a, b\) 在同一颗子树内,他们聚合的点显然是最近公共祖先,因为如果再往上走,2的条件就不满足。
  3. \(a, b\) 在同一联通量(同一颗基环树),两颗不同的子树内。显然他们必须走到树根,然后这时有两个选择:从 \(root[a]\) 走到 \(root[b]\),或反之。我们求出两种走的步数,按照题意进行比较即可。

第一次遇到内向树,找基环的时候不知道该如何搞。(偷瞄了一眼别人的代码)发现他是自底向上进行递归,跟往常我们写的树的自顶向下相反,学习了一下。

相比来说自底向上貌似对内向树代码简化很多(只需一次dfs),原因在于:

  1. 若自顶向下遍历,我们找到一个环的时候,可能他的子树中的某些点已经遍历过了,也就是说 \(dep\)\(root\) 等数组都算的不对,可能需要二次 \(dfs\)
  2. 若自底向上遍历,只有当找到环之后,一个点才会确保更新。
#include 
#include 
#include 
using namespace std;
const int N = 500005, L = 19;
int n, Q, fa[N][L], f[N], root[N], dep[N], sum[N], ult[N];
bool vis[N], ins[N];
int find(int x) { return x == f[x] ? x : f[x] = find(f[x]); }
void dfs(int u) {
    vis[u] = ins[u] = true;
    int v = fa[u][0];
    if (ins[v]) {
        // 此时从 v -> fa[v][0], ->..., -> u -> v... 构成一个环
        int cnt = 0, y = v;
        while (1) {
            root[y] = y, dep[y] = 0, ult[y] = u;
            sum[y] = ++cnt, ins[y] = false;
            if (y == u) break;
            y = fa[y][0];
        }
        return;
    }
    if (!vis[v]) dfs(v);
    if (!root[u]) root[u] = root[v], dep[u] = dep[v] + 1, ins[u] = false;
    for (int i = 1; i = dep[y]) x = fa[x][i];
    if (x == y) return x;
    for (int i = L - 1; ~i; i--)
        if (fa[x][i] != fa[y][i]) x = fa[x][i], y = fa[y][i];
    return fa[x][0];
}
int main() {
    scanf("%d%d", &n, &Q);
    for (int i = 1; i = y))))
                printf("%d %d\n", x + cx, y);
            else printf("%d %d\n", x, y + cy);
        }
    }
    return 0;
}

AcWing 392. 会合

标签:简化   公共祖先   swa   math   ace   main   不难   如何   lin   

原文地址:https://www.cnblogs.com/dmoransky/p/12310510.html


评论


亲,登录后才可以留言!