[NOIP 2007 提高组] 树网的核

GitHub跳转原题关系图返回列表

先求带权树直径,再用双指针枚举直径上的长度不超过 s 的核心区间。

OJ: luogu

题目 ID: P1099

难度:提高

标签:树的直径双指针贪心python

日期: 2026-07-17 02:00

题意

在带权树的一条直径上选长度不超过 s 的路径,使所有节点到它的最大距离最小。

思路

两次树上最远点搜索得到直径端点 A,B。对直径节点 k,直径外分支深度不会超过它到任一端点的距离;因此区间 [i,j] 的偏心距是 max(dist(A,i), dist(j,B), 最大分支深度)。直径前缀距离单调,双指针找每个 i 能到达的最右 j

Python 知识

  • for node in order 可以遍历一个不断 append 的列表,适合非递归树遍历。
  • max(order, key=distance.__getitem__) 按数组值找最远点。
  • 字典 position 把直径节点集合变成 O(1) 的分支判断。

代码

python
import sys


input = sys.stdin.buffer.readline
n, limit = map(int, input().split())
graph = [[] for _ in range(n + 1)]
for _ in range(n - 1):
    u, v, weight = map(int, input().split())
    graph[u].append((v, weight))
    graph[v].append((u, weight))


def farthest(start):
    parent = [0] * (n + 1)
    distance = [0] * (n + 1)
    order = [start]
    for node in order:
        for neighbor, weight in graph[node]:
            if neighbor != parent[node]:
                parent[neighbor] = node
                distance[neighbor] = distance[node] + weight
                order.append(neighbor)
    end = max(order, key=distance.__getitem__)
    return end, parent, distance


diameter_start, _, _ = farthest(1)
diameter_end, parent, distance = farthest(diameter_start)
diameter = []
node = diameter_end
while node:
    diameter.append(node)
    if node == diameter_start:
        break
    node = parent[node]
diameter.reverse()
position = {node: index for index, node in enumerate(diameter)}
coordinate = [distance[node] for node in diameter]

branch_maximum = 0
for root in diameter:
    stack = [(root, 0, 0)]
    while stack:
        node, previous, branch_distance = stack.pop()
        branch_maximum = max(branch_maximum, branch_distance)
        for neighbor, weight in graph[node]:
            if neighbor != previous and neighbor not in position:
                stack.append((neighbor, node, branch_distance + weight))

diameter_length = coordinate[-1]
answer = diameter_length
right = 0
for left in range(len(diameter)):
    right = max(right, left)
    while right + 1 < len(diameter) and coordinate[right + 1] - coordinate[left] <= limit:
        right += 1
    answer = min(answer, max(coordinate[left], diameter_length - coordinate[right], branch_maximum))
print(answer)

原有 C++ 版本仍保留:

cpp
/**
 * Author by Rainboy blog: https://rainboylv.com github: https://github.com/rainboylvx
 * rbook: -> https://rbook.roj.ac.cn  https://rbook2.roj.ac.cn
 * rainboy的学习导航网站: https://idx.roj.ac.cn
 * create_at: 2026-07-17 01:04
 * update_at: 2026-07-17 01:04
 */
#include <bits/stdc++.h>
using namespace std;

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    return 0;
}

复杂度

时间 O(n),空间 O(n)

总结

带权树的核问题先降到唯一的直径,再把长度约束转成单调双指针。