问题描述
这天, 小明在机房学习。
他发现机房里一共有 𝑛 台电脑, 编号为 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;
}
}