蓝桥杯第2198题——机房(LCA+dp)

问题描述

这天, 小明在机房学习。

他发现机房里一共有 𝑛 台电脑, 编号为 1 到 𝑛, 电脑和电脑之间有网线连 接, 一共有 𝑛−1 根网线将 𝑛 台电脑连接起来使得任意两台电脑都直接或者间 接地相连。

小明发现每台电脑转发、发送或者接受信息需要的时间取决于这台电脑和 多少台电脑直接相连, 而信息在网线中的传播时间可以忽略。比如如果某台电脑 用网线直接连接了另外 𝑑 台电脑, 那么任何经过这台电脑的信息都会延迟 𝑑 单 位时间 (发送方和接收方也会产生这样的延迟, 当然如果发送方和接收方都是 同一台电脑就只会产生一次延迟)。

小明一共产生了 𝑚 个疑问: 如果电脑 𝑢𝑖 向电脑 𝑣𝑖i 发送信息, 那么信息从 𝑢𝑖 传到 𝑣𝑖​ 的最短时间是多少?

输入格式

输入共 𝑛+𝑚 行, 第一行为两个正整数 𝑛,𝑚 。

后面 𝑛−1 行, 每行两个正整数 𝑥,𝑦 表示编号为 𝑥 和 𝑦 的两台电脑用网线 直接相连。

后面 𝑚 行, 每行两个正整数 𝑢𝑖,𝑣𝑖​ 表示小明的第 𝑖 个疑问。

输出格式

输出共 𝑚m 行, 第 𝑖 行一个正整数表示小明第 𝑖 个疑问的答案。

样例输入

4 3
1 2
1 3
2 4
2 3
3 4
3 3

样例输出

5
6
1

样例说明

这四台电脑各自的延迟分别为 2,2,1,1。

对于第一个询问, 从 2 到 3 需要经过 2,1,3 所以时间和为 2+2+1=5 。

对于第二个询问, 从 3 到 4 需要经过 3,1,2,4 所以时间和为 1+2+2+1=6。

对于第三个询问, 从 3 到 3 只会产生一次延迟, 所以时间为 1 。

评测用例规模与约定

对于 30% 的数据, 保证 𝑛,𝑚≤1000;

对于 100% 的数据, 保证 𝑛,𝑚≤100000 。

解题思路

这道题根据n个点n-1条边可以确定是一棵树,那么两点之间的最短距离就是基于LCA最近公共祖先了,这道题笔者考虑使用倍增法解决。

根据题意分析可知,对于两点之间的距离,是基于两点之间所有点的度数和,那么对于单条链路的距离,我们可以建模为双向不同权的边,一条边的长度就是起始点的度。

定义dp[i][j]表示从节点i开始向上跳2的j次方步所经过的距离:dp[i][j] = dp[i][j-1] + dp[st[i][j-1]][j-1]

其中st[i][j]表示从节点i开始向上跳2的j次方步之后的节点编号,是LCA的模板。

我们修改原LCA中返回最近公共祖先,将其改为返回总链路长度,我们基于原LCA中对a和b节点的跳跃,在其中添加对于dp的访问,由于总链路长度是x个点的度数和,而我们只计算了其中的x-1条边的长度,那么最后在返回答案时添加一个最近公共祖先节点的度数即可。

import java.util.*;

public class Main {
    
    static int n, m;

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        n = sc.nextInt(); m = sc.nextInt();
        init();
        for (int i = 1; i <= n - 1; i++) {
            int u = sc.nextInt();
            int v = sc.nextInt();
            data[i][0] = u;
            data[i][1] = v;
            outer[u]++;
            outer[v]++;
        }
        for (int[] row : data) {
            addEdge(row[0], row[1]);
            addEdge(row[1], row[0]);
        }
        build();
        dfs(1, 0);
        for (int i = 0; i < m; i++) {
            int a = sc.nextInt();
            int b = sc.nextInt();
            System.out.println(lca(a, b));
        }
    }
    
    static long lca(int a, int b) {
        long ans = 0;
        if (deep[a] < deep[b]) {
            int temp = a; a = b; b = temp;
        }
        for (int i = log2(n) + 1; i >= 0; i--) {
            if (deep[st[a][i]] >= deep[b]) {
                ans += dp[a][i];
                a = st[a][i];
            }
        }
        if (a == b) {
            return ans + outer[a];
        }
        for (int i = log2(n) + 1; i >= 0; i--) {
            if (st[a][i] != st[b][i]) {
                ans += dp[a][i];
                ans += dp[b][i];
                a = st[a][i];
                b = st[b][i];
            }
        }
        return ans + dp[a][0] + dp[b][0] + outer[st[a][0]];
    }
    
    static void dfs(int s, int fa) {
        deep[s] = deep[fa] + 1;
        st[s][0] = fa;
        for (int i = 1; i <= log2(n) + 1; i++) {
            st[s][i] = st[st[s][i - 1]][i - 1];
        }
        for (int i = head[s]; i != -1; i = edges[i].next) {
            if (edges[i].v == fa) {
                dp[s][0] = edges[i].w;
                break;
            }
        }
        for (int i = 1; i <= log2(n) + 1; i++) {
            dp[s][i] = dp[s][i - 1] + dp[st[s][i - 1]][i - 1];
        }
        for (int i = head[s]; i != -1; i = edges[i].next) {
            if (edges[i].v != fa) {
                dfs(edges[i].v, s);
            }
        }
    }
    
    static int[][] st;
    static int[] deep;
    static int[][] dp;
    
    static void build() {
        st = new int[n + 1][log2(n) + 2];
        deep = new int[n + 1];
        dp = new int[n + 1][log2(n) + 2];
    }
    
    static void addEdge(int u, int v) {
        edges[cnt] = new Edge(v, outer[u], head[u]);
        head[u] = cnt++;
    }
    
    static Edge[] edges;
    static int[] head;
    static int cnt;
    static int[] outer;
    static int[][] data;
    
    static void init() {
        edges = new Edge[(n + 1) << 1];
        head = new int[n + 1];
        Arrays.fill(head, -1);
        cnt = 1;
        outer = new int[n + 1];
        data = new int[n][2];
    }
    
    static int log2(int x) {
        int res = 0;
        while ((1 << res) <= (x >> 1)) {
            res++;
        }
        return res;
    }
}


class Edge{
    int v, w, next;

    public Edge(int v, int w, int next) {
        super();
        this.v = v;
        this.w = w;
        this.next = next;
    }
    
}

  • 5
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
LCA+路径压缩的方式可以用于求解树上的桥,具体实现步骤如下: 1. 对于树上每个节点,记录其在树中的深度(或者高度)以及其父亲节点。 2. 对于每个节点,记录其在树上的最小深度(或最小高度)以及其所在子树中深度最小的节点。 3. 对于每条边(u, v),设u的深度小于v的深度(或者高度),则如果v的子树中没有深度小于u的节点,则(u, v)是桥。 具体的实现过程如下: 首先,我们需要对树进行预处理,求出每个节点的深度以及其父亲节点。可以使用深度优先搜索(DFS)或广度优先搜索(BFS)来实现。在这里我们使用DFS来实现: ```c++ vector<int> adj[MAX_N]; // 树的邻接表 int n; // 树的节点数 int dep[MAX_N], fa[MAX_N]; // dep[i]表示节点i的深度,fa[i]表示节点i的父亲节点 void dfs(int u, int f, int d) { dep[u] = d; fa[u] = f; for (int v : adj[u]) { if (v != f) { dfs(v, u, d + 1); } } } ``` 接下来,我们需要计算每个节点所在子树中深度最小的节点。我们可以使用LCA(最近公共祖先)的方法来实现。具体来说,我们可以使用倍增算法来预处理出每个节点的2^k级祖先,并且在查询LCA时使用路径压缩的方式优化时间复杂度。这里我们不展开讲解LCA和倍增算法的细节,如果你对此感兴趣,可以参考其他资料进行学习。 ```c++ const int MAX_LOG_N = 20; // log2(n)的上取整 int anc[MAX_N][MAX_LOG_N]; // anc[i][j]表示节点i的2^j级祖先 int mn[MAX_N]; // mn[i]表示节点i所在子树中深度最小的节点 void precompute() { // 预处理anc数组 for (int j = 1; j < MAX_LOG_N; j++) { for (int i = 1; i <= n; i++) { if (anc[i][j - 1] != -1) { anc[i][j] = anc[anc[i][j - 1]][j - 1]; } } } // 计算mn数组 for (int i = 1; i <= n; i++) { mn[i] = i; for (int j = 0; (1 << j) <= dep[i]; j++) { if ((dep[i] & (1 << j)) != 0) { mn[i] = min(mn[i], mn[anc[i][j]]); i = anc[i][j]; } } } } ``` 最后,我们可以使用LCA+路径压缩的方式来判断每条边是否为桥。具体来说,对于每条边(u, v),我们需要判断v的子树中是否存在深度小于u的节点。如果存在,则(u, v)不是桥,否则(u, v)是桥。 ```c++ bool is_bridge(int u, int v) { if (dep[u] > dep[v]) swap(u, v); if (mn[v] != u) return true; // 子树中存在深度小于u的节点 return false; // 子树中不存在深度小于u的节点 } ``` 完整代码如下:
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值