【题目链接】
【思路要点】
- 首先,本题一点重要的观察是,新建的路径的两个端点必定在树的直径上,若一个方案新建路径的两个端点有一个不在直径上,我们令其向直径靠近,不会使答案变劣。
- 因此,我们可以将直径拿出来考虑,令直径上点数为 t o t tot tot ,每一个点为 p o s i pos_i posi , p o s i pos_i posi 与 p o s i − 1 pos_{i-1} posi−1 在树上的连边长度为 l e n i len_i leni , p r e i = ∑ j = 1 i l e n j pre_i=\sum_{j=1}^{i}len_j prei=∑j=1ilenj 。
- 如果我们切断直径上所有的边,树会分成 t o t tot tot 块,每一块的直径是无可避免的,令 L L L 为每一块直径的最大值,则答案至少为 L L L ,至多为直径长度 R R R 。令 d o w n i down_i downi 为从 p o s i pos_i posi 出发在第 i i i 块内的最长路径的长度。
- 有了答案的区间 [ L , R ] [L,R] [L,R] ,考虑二分答案 m i d mid mid ,判断是否存在使得所有距离不超过 m i d mid mid 的连边方案。
- 假设我们最终的连边为 a → b ( a < b ) a\rightarrow b\ (a<b) a→b (a<b) ,那么对于任意一对 i < j i<j i<j ,以下两点至少有一点成立是存在方案的充要条件:
1 1 1 、 d o w n i + d o w n j + p r e j − p r e i ≤ m i d down_i+down_j+pre_j-pre_i≤mid downi+downj+prej−prei≤mid
2 2 2 、 d o w n i + d o w n j + ∣ p r e i − p r e a ∣ + ∣ p r e j − p r e b ∣ + L e n ≤ m i d down_i+down_j+|pre_i-pre_a|+|pre_j-pre_b|+Len≤mid downi+downj+∣prei−prea∣+∣prej−preb∣+Len≤mid ,其中 L e n Len Len 为添加的边长- 由于要求是 ≤ m i d ≤mid ≤mid ,我们可以枚举 2 2 2 式中的两个绝对值的展开方式,将其转化为 4 4 4 个不带绝对值的限制。
- 对 p o s i pos_i posi 分别按照 d o w n i + p r e i , d o w n i − p r e i down_i+pre_i,down_i-pre_i downi+prei,downi−prei 排序,在第一个顺序中枚举 j j j ,对应满足 1 1 1 式的 i i i 应当是第二个顺序的一个前缀,忽略掉满足 1 1 1 式的 ( i , j ) (i,j) (i,j) ,对于剩余的一定要满足 2 2 2 式的 ( i , j ) (i,j) (i,j) ,我们分别求出出现过最紧的限制 l i m i t 1 , l i m i t 2 , l i m i t 3 , l i m i t 4 limit_1,limit_2,limit_3,limit_4 limit1,limit2,limit3,limit4 。
- 接下来问题转化为了判断是否存在 ( a , b ) (a,b) (a,b) ,满足
1 1 1 、 − p r e a − p r e b ≤ l i m i t 1 -pre_a-pre_b≤limit_1 −prea−preb≤limit1
2 2 2 、 − p r e a + p r e b ≤ l i m i t 2 -pre_a+pre_b≤limit_2 −prea+preb≤limit2
3 3 3 、 p r e a − p r e b ≤ l i m i t 3 pre_a-pre_b≤limit_3 prea−preb≤limit3
4 4 4 、 p r e a + p r e b ≤ l i m i t 4 pre_a+pre_b≤limit_4 prea+preb≤limit4- 从小到大枚举 a a a ,满足以上四个限制的 b b b 均为一个前缀或是后缀,且其端点具有单调性,可用 T w o P o i n t e r s TwoPointers TwoPointers 维护,判断时判断合法的 b b b 的位置是否为空即可。
- 时间复杂度 O ( N L o g N + N L o g V ) O(NLogN+NLogV) O(NLogN+NLogV) 。
【代码】
#include<bits/stdc++.h> using namespace std; const int MAXN = 2e5 + 5; const long long INF = 1e18; typedef long long ll; typedef long double ld; typedef unsigned long long ull; template <typename T> void chkmax(T &x, T y) {x = max(x, y); } template <typename T> void chkmin(T &x, T y) {x = min(x, y); } template <typename T> void read(T &x) { x = 0; int f = 1; char c = getchar(); for (; !isdigit(c); c = getchar()) if (c == '-') f = -f; for (; isdigit(c); c = getchar()) x = x * 10 + c - '0'; x *= f; } template <typename T> void write(T x) { if (x < 0) x = -x, putchar('-'); if (x > 9) write(x / 10); putchar(x % 10 + '0'); } template <typename T> void writeln(T x) { write(x); puts(""); } int n; ll len, l, r; bool vis[MAXN]; vector <pair <int, int> > a[MAXN]; int dfsans, father[MAXN], fedge[MAXN]; ll sum[MAXN]; int tot, pos[MAXN], pa[MAXN], pb[MAXN]; ll down[MAXN], pre[MAXN]; ll limit1, limit2, limit3, limit4; //pa : down + pre, pb : down - pre; //1 : --, 2 : -+, 3 : +-, 4 : ++. bool check(ll mid) { limit1 = limit2 = limit3 = limit4 = INF; ll Min1 = INF, Min2 = INF, Min3 = INF, Min4 = INF; for (int j = 1, i = tot; j <= tot; j++) { while (i != 0 && down[pb[i]] - pre[pb[i]] + down[pa[j]] + pre[pa[j]] > mid) { int tmp = pb[i--]; chkmin(Min1, -down[tmp] - pre[tmp]); chkmin(Min2, -down[tmp] - pre[tmp]); chkmin(Min3, -down[tmp] + pre[tmp]); chkmin(Min4, -down[tmp] + pre[tmp]); } int tmp = pa[j]; chkmin(limit1, -down[tmp] - pre[tmp] + Min1); chkmin(limit2, -down[tmp] + pre[tmp] + Min2); chkmin(limit3, -down[tmp] - pre[tmp] + Min3); chkmin(limit4, -down[tmp] + pre[tmp] + Min4); } limit1 += mid - len, limit2 += mid - len; limit3 += mid - len, limit4 += mid - len; int pos1 = tot + 1, pos2 = 0, pos3 = 1, pos4 = tot; for (int i = 1; i <= tot; i++) { while (pos1 - 1 >= 1 && -pre[i] - pre[pos1 - 1] <= limit1) pos1--; while (pos2 + 1 <= tot && -pre[i] + pre[pos2 + 1] <= limit2) pos2++; while (pos3 <= tot && pre[i] - pre[pos3] > limit3) pos3++; while (pos4 >= 1 && pre[i] + pre[pos4] > limit4) pos4--; int l = max(pos1, pos3), r = min(pos2, pos4); if (l <= r) return true; } return false; } void dfs(int pos, int fa) { if (sum[pos] > sum[dfsans]) dfsans = pos; for (auto x : a[pos]) if (x.first != fa && !vis[x.first]) { sum[x.first] = sum[pos] + x.second; fedge[x.first] = x.second; father[x.first] = pos; dfs(x.first, pos); } } int dfs(int from) { dfsans = from; sum[from] = father[from] = 0; dfs(from, 0); return dfsans; } int main() { for (scanf("%d%lld", &n, &len); n != 0; scanf("%d%lld", &n, &len)) { for (int i = 1; i <= n; i++) a[i].clear(); for (int i = 1; i <= n - 1; i++) { int x, y, z; read(x), read(y), read(z); a[x].emplace_back(y, z); a[y].emplace_back(x, z); } memset(vis, false, sizeof(vis)), tot = 0; for (int i = dfs(dfs(1)); i != 0; i = father[i]) { pos[++tot] = i; vis[i] = true; pre[tot + 1] = fedge[i]; } l = 0; for (int i = 1; i <= tot; i++) { pre[i] += pre[i - 1]; pa[i] = pb[i] = i; int now = pos[i]; vis[now] = false; int tmp = dfs(now); down[i] = sum[tmp]; chkmax(l, sum[dfs(tmp)]); vis[now] = true; } sort(pa + 1, pa + tot + 1, [&] (int x, int y) {return down[x] + pre[x] < down[y] + pre[y]; }); sort(pb + 1, pb + tot + 1, [&] (int x, int y) {return down[x] - pre[x] < down[y] - pre[y]; }); r = pre[tot]; while (l < r) { ll mid = (l + r) / 2; if (check(mid)) r = mid; else l = mid + 1; } printf("%lld\n", l); } return 0; }