【题目链接】
【思路要点】
- 首先,题目要求求解平均值的最大值,考虑分数规划。二分答案\(Mid\),将树上每条边权减去\(Mid\),问题变为树上是否存在边数在\(L\)到\(R\)之间的权值之和非负的路径。考虑点分治。
- 记录\(Dist\)数组,\(Dist_{i}\)表示边数为\(i\)的从分治根节点出发的路径的最大权值。
- 对于每一个分治子树进行BFS,记当前BFS深度为\(i\),在\(Dist\)数组上使用单点队列维护区间\([max(0,L-i),min(R-i,Depth_{root})]\)最大值。
- 若如此做,每个分治重心处处理的时间复杂度为\(O(\sum_{i=1}^{子树数量}(Size_{i}+max_{j=1}^{i-1}\{Depth_{j}\}))\),该复杂度可能退化为平方级别,导致超时。
- 考虑对每个子树根据其深度排序(基数排序),这样每个分治重心处处理的时间复杂度变为\(O(\sum_{i=1}^{子树数量}(Size_{i}+Depth_{i-1}))=O(Size_{分治重心})\)。即可通过。
- 另外,由于时限较紧,需要先将点分树构建出来才能够通过。
【代码】
#include<bits/stdc++.h> using namespace std; #define MAXN 100005 #define INF 1e9 #define EPS 1e-5 struct edge {int dest; double len; }; vector <edge> a[MAXN], b[MAXN]; vector <int> c[MAXN]; int n, L, R, root, Root, ArrSize; double delta, value[MAXN]; bool found, visited[MAXN]; int size[MAXN], weight[MAXN], depth[MAXN], Depth[MAXN]; void getroot(int pos, int father, int total) { size[pos] = 1; depth[pos] = 0; weight[pos] = 0; for (unsigned i = 0; i<a[pos].size(); i++) if (!visited[a[pos][i].dest] && a[pos][i].dest != father) { getroot(a[pos][i].dest, pos, total); size[pos] += size[a[pos][i].dest]; weight[pos] = max(weight[pos], size[a[pos][i].dest]); depth[pos] = max(depth[pos], depth[a[pos][i].dest]); } depth[pos]++; weight[pos] = max(weight[pos], total - size[pos]); if (weight[pos] < weight[root]) root = pos; } void prework(int pos, int total) { visited[pos] = true; getroot(pos, 0, total); static int cnt[MAXN], rank[MAXN]; int maxnum = 0; for (unsigned i = 0; i<a[pos].size(); i++) if (!visited[a[pos][i].dest]) { maxnum = max(maxnum, depth[a[pos][i].dest]); cnt[depth[a[pos][i].dest]]++; } Depth[pos] = maxnum; for (int i = 1; i <= maxnum; i++) cnt[i] += cnt[i - 1]; int curr = 0; for (unsigned i = 0; i<a[pos].size(); i++) if (!visited[a[pos][i].dest]) { rank[cnt[depth[a[pos][i].dest]]--] = i; curr++; } for (int i = 1; i <= curr; i++) b[pos].push_back(a[pos][rank[i]]); for (int i = 1; i <= maxnum; i++) cnt[i] = 0; for (unsigned i = 0; i<a[pos].size(); i++) if (!visited[a[pos][i].dest]) { root = 0; getroot(a[pos][i].dest, 0, size[a[pos][i].dest]); c[pos].push_back(root); prework(root, size[a[pos][i].dest]); } } void getans(int pos, double len) { static int q[MAXN], f[MAXN], Q[MAXN], dist[MAXN]; static double Len[MAXN]; int l = 0, r = 0, QL = 0, QR = - 1, point = min(R, ArrSize); while (point >= L) { while (QR >= QL && value[point]>value[Q[QR]]) QR--; Q[++QR] = point; point--; } if (QL <= QR && value[Q[QL]] >= 0) found = true; q[0] = pos; f[0] = 0; dist[0] = 1; Len[0] = len; while (l <= r) { while (QL <= QR && dist[l] + Q[QL]>R) QL++; while (point >= 0 && point + dist[l] >= L) { while (QR >= QL && value[point]>value[Q[QR]]) QR--; Q[++QR] = point; point--; } if (QL <= QR && Len[l] + value[Q[QL]] >= 0) found = true; int tmp = q[l]; for (unsigned i = 0; i<a[tmp].size(); i++) if (!visited[a[tmp][i].dest] && a[tmp][i].dest != f[l]) { int next = a[tmp][i].dest; r++; q[r] = next; f[r] = tmp; dist[r] = dist[l] + 1; Len[r] = Len[l] + a[tmp][i].len - delta; } l++; } ArrSize = dist[r]; for (int i = 0; i <= r; i++) value[dist[i]] = max(value[dist[i]], Len[i]); } void work(int pos) { visited[pos] = true; ArrSize = 0; for (unsigned i = 0; i<b[pos].size(); i++) getans(b[pos][i].dest, b[pos][i].len - delta); for (int i = 1; i <= ArrSize; i++) value[i] = -INF; for (unsigned i = 0; i<c[pos].size(); i++) work(c[pos][i]); } void init() { memset(visited, false, sizeof(visited)); size[0] = weight[0] = n; root = 0; getroot(1, 0, n); Root = root; prework(root, n); value[0] = 0; for (int i = 1; i <= n; i++) value[i] = -INF; } bool check(double x) { delta = x; memset(visited, false, sizeof(visited)); found = false; work(Root); return found; } int main() { scanf("%d%d%d", &n, &L, &R); double l = INF, r = 0; for (int i = 1; i<n; i++) { int x, y; double z; scanf("%d%d%lf", &x, &y, &z); a[x].push_back((edge){y, z}); a[y].push_back((edge){x, z}); r = max(r, z); l = min(l, z); } init(); while (l + EPS < r) { double mid = (l + r) / 2; if (check(mid)) l = mid; else r = mid; } printf("%.3lf\n", (l + r) / 2 + EPS); return 0; }