• AcWing 392. 会合


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

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

    把每一个基环抠出来。

    大力分类讨论:

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

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

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

    1. 若自顶向下遍历,我们找到一个环的时候,可能他的子树中的某些点已经遍历过了,也就是说 (dep)(root) 等数组都算的不对,可能需要二次 (dfs)
    2. 若自底向上遍历,只有当找到环之后,一个点才会确保更新。
    #include <cstdio>
    #include <iostream>
    #include <vector>
    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 < L && fa[u][i - 1]; i++) fa[u][i] = fa[fa[u][i - 1]][i - 1];
    }
    int lca(int x, int y) {
        if (dep[x] < dep[y]) swap(x, y);
        for (int i = L - 1; ~i; i--)
            if (dep[x] - (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 <= n; i++) f[i] = i;
    	for (int i = 1; i <= n; i++) scanf("%d", &fa[i][0]), f[find(i)] = find(fa[i][0]);
    	for (int i = 1; i <= n; i++) if (!vis[i]) dfs(i);
    	while(Q--) {
    		int a, b; scanf("%d%d", &a, &b);
    		if (find(a) != find(b)) puts("-1 -1");
    		else if(root[a] == root[b]) {
    		    int p = lca(a, b);
    		    printf("%d %d
    ", dep[a] - dep[p], dep[b] - dep[p]);
    		} else {
    		    int x = dep[a], y = dep[b], cx, cy;
    		    if (sum[root[a]] < sum[root[b]]) cx = sum[root[b]] - sum[root[a]], cy = sum[ult[root[a]]] - cx;
    		    else cy = sum[root[a]] - sum[root[b]], cx = sum[ult[root[a]]] - cy;
    		    if (max(x + cx, y) < max(x, y + cy) || (max(x + cx, y) == max(x, y + cy) && (min(x + cx, y) < min(x, y + cy) || (min(x + cx, y) == min(x, y + cy) && x + cx >= y))))
    		        printf("%d %d
    ", x + cx, y);
    		    else printf("%d %d
    ", x, y + cy);
    		}
    	}
    	return 0;
    }
    
  • 相关阅读:
    js/jquery 页面传值
    php 连接sqlserver方法
    php 写webservice常见问题
    php 解决json_encode中文null和UNICODE转码问题
    手机web——自适应网页设计
    50个js技巧(分享)
    php webservice客户端和服务器端
    php 文件下载功能
    es6 字符串的扩展
    vue $emit抛出事件
  • 原文地址:https://www.cnblogs.com/dmoransky/p/12310510.html
Copyright © 2020-2023  润新知