题意
题解
考虑朴素的算法,枚举边权取零的边,统计路径权和,更新答案。总时间复杂度 O ( N 2 ) O(N^2) O(N2),显然难以胜任。
难以直接求解最短时间,那么考虑已知一个时间上界,如何验证是否存在一个边权取零的方案,使所需时间小于等于这个上界。设上界为 l i m lim lim,运输计划 i i i 所需时间为 D i D_i Di,对于 D i > l i m D_i>lim Di>lim 的情况,权值取零的边一定要属于运输计划 i i i 对应的路径。那么权值取零的边的可能集合,即所有满足 D i > l i m D_i>lim Di>lim 的路径的公共边,根据贪心策略,取其中权值最大的边可以使所需时间最短。
路径的公共边容易通过树上差分求解。具体而言,进行树上边差分,然后取前缀和,将运输路径 i i i 上,从 U i U_i Ui 到 V i V_i Vi 的边记录值 c n t cnt cnt 增加一。那么任一树边的记录值,代表它被多少条运输路径所覆盖。显然,路径覆盖数等于满足 D i > l i m D_i>lim Di>lim 的路径数的边,属于决策集合。
倍增预处理 L C A LCA LCA,二分答案,总时间复杂度 O ( ( N + M ) log N ) O\Big((N+M)\log N\Big) O((N+M)logN)。
#include <bits/stdc++.h>
using namespace std;
const int maxn = 300005, maxt = 1005, maxlg = 19;
int N, M, U[maxn], V[maxn], LCA[maxn], D[maxn], cnt[maxn];
int E, head[maxn], to[maxn * 2], cost[maxn * 2], nxt[maxn * 2];
int lg[maxn], dep[maxn], ds[maxn], par[maxn][maxlg];
void add_edge(int u, int v, int w) { to[E] = v, cost[E] = w, nxt[E] = head[u], head[u] = E++; }
void dfs(int u, int p, int d, int w)
{
par[u][0] = p, dep[u] = d, ds[u] = w;
for (int i = 1; i <= lg[dep[u]]; ++i)
par[u][i] = par[par[u][i - 1]][i - 1];
for (int i = head[u], v; ~i; i = nxt[i])
if ((v = to[i]) != p)
dfs(v, u, d + 1, w + cost[i]);
}
int lca(int u, int v)
{
if (dep[u] < dep[v])
swap(u, v);
while (dep[u] > dep[v])
u = par[u][lg[dep[u] - dep[v]]];
if (u == v)
return u;
for (int k = lg[dep[u]]; k >= 0;)
if (par[u][k] != par[v][k])
u = par[u][k], v = par[v][k], k = lg[dep[u]];
else
--k;
return par[u][0];
}
void get_sum(int u, int p, int num, int &res)
{
int d = 0;
for (int i = head[u]; ~i; i = nxt[i])
{
int v = to[i];
if (v == p)
d = cost[i];
else
get_sum(v, u, num, res), cnt[u] += cnt[v];
}
if (cnt[u] == num)
res = max(res, d);
}
bool judge(int lim)
{
int num = 0, mx_d = 0;
memset(cnt, 0, sizeof(cnt));
for (int i = 0; i < M; ++i)
if (D[i] > lim)
{
++num, mx_d = max(mx_d, D[i]);
++cnt[U[i]], ++cnt[V[i]], cnt[LCA[i]] -= 2;
}
int mx_t = 0;
get_sum(0, -1, num, mx_t);
return mx_d - mx_t <= lim;
}
int main()
{
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
cin >> N >> M;
memset(head, -1, sizeof(head));
for (int i = 1; i < N; ++i)
{
int a, b, t;
cin >> a >> b >> t;
--a, --b;
add_edge(a, b, t), add_edge(b, a, t);
}
for (int i = 0; i < M; ++i)
cin >> U[i] >> V[i], --U[i], --V[i];
lg[0] = -1;
for (int i = 1; i < N; ++i)
lg[i] = lg[i >> 1] + 1;
dfs(0, -1, 0, 0);
for (int i = 0; i < M; ++i)
LCA[i] = lca(U[i], V[i]), D[i] = ds[U[i]] + ds[V[i]] - ds[LCA[i]] * 2;
int lb = -1, ub = N * maxt;
while (ub - lb > 1)
{
int mid = (lb + ub) >> 1;
if (judge(mid))
ub = mid;
else
lb = mid;
}
cout << ub << '\n';
return 0;
}