版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_39972971/article/details/83661841
【题目链接】
【思路要点】
- 首先,本题一点重要的观察是,新建的路径的两个端点必定在树的直径上,若一个方案新建路径的两个端点有一个不在直径上,我们令其向直径靠近,不会使答案变劣。
- 因此,我们可以将直径拿出来考虑,令直径上点数为 ,每一个点为 , 与 在树上的连边长度为 , 。
- 如果我们切断直径上所有的边,树会分成 块,每一块的直径是无可避免的,令 为每一块直径的最大值,则答案至少为 ,至多为直径长度 。令 为从 出发在第 块内的最长路径的长度。
- 有了答案的区间 ,考虑二分答案 ,判断是否存在使得所有距离不超过 的连边方案。
- 假设我们最终的连边为 ,那么对于任意一对 ,以下两点至少有一点成立是存在方案的充要条件:
、
、 ,其中 为添加的边长- 由于要求是 ,我们可以枚举 式中的两个绝对值的展开方式,将其转化为 个不带绝对值的限制。
- 对 分别按照 排序,在第一个顺序中枚举 ,对应满足 式的 应当是第二个顺序的一个前缀,忽略掉满足 式的 ,对于剩余的一定要满足 式的 ,我们分别求出出现过最紧的限制 。
- 接下来问题转化为了判断是否存在 ,满足
、
、
、
、- 从小到大枚举 ,满足以上四个限制的 均为一个前缀或是后缀,且其端点具有单调性,可用 维护,判断时判断合法的 的位置是否为空即可。
- 时间复杂度 。
【代码】
#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; }