[NOIP2015 提高组] 运输计划
题目背景
公元 $2044$ 年,人类进入了宇宙纪元。
题目描述
公元 $2044$ 年,人类进入了宇宙纪元。
L 国有 $n$ 个星球,还有 $n-1$ 条双向航道,每条航道建立在两个星球之间,这 $n-1$ 条航道连通了 L 国的所有星球。
小 P 掌管一家物流公司, 该公司有很多个运输计划,每个运输计划形如:有一艘物流飞船需要从 $u_i$ 号星球沿最快的宇航路径飞行到 $v_i$ 号星球去。显然,飞船驶过一条航道是需要时间的,对于航道 $j$,任意飞船驶过它所花费的时间为 $t_j$,并且任意两艘飞船之间不会产生任何干扰。
为了鼓励科技创新, L 国国王同意小 P 的物流公司参与 L 国的航道建设,即允许小 P 把某一条航道改造成虫洞,飞船驶过虫洞不消耗时间。
在虫洞的建设完成前小 P 的物流公司就预接了 $m$ 个运输计划。在虫洞建设完成后,这 $m$ 个运输计划会同时开始,所有飞船一起出发。当这 $m$ 个运输计划都完成时,小 P 的物流公司的阶段性工作就完成了。
如果小 P 可以自由选择将哪一条航道改造成虫洞, 试求出小 P 的物流公司完成阶段性工作所需要的最短时间是多少?
输入格式
第一行包括两个正整数 $n, m$,表示 L 国中星球的数量及小 P 公司预接的运输计划的数量,星球从 $1$ 到 $n$ 编号。
接下来 $n-1$ 行描述航道的建设情况,其中第 $i$ 行包含三个整数 $a_i, b_i$ 和 $t_i$,表示第 $i$ 条双向航道修建在 $a_i$ 与 $b_i$ 两个星球之间,任意飞船驶过它所花费的时间为 $t_i$。
数据保证
接下来 $m$ 行描述运输计划的情况,其中第 $j$ 行包含两个正整数 $u_j$ 和 $v_j$,表示第 $j$ 个运输计划是从 $u_j$ 号星球飞往 $v_j$号星球。
输出格式
一个整数,表示小 P 的物流公司完成阶段性工作所需要的最短时间。
样例 #1
样例输入 #1
6 3
1 2 3
1 6 4
3 1 7
4 3 6
3 5 5
3 6
2 5
4 5
样例输出 #1
11
提示
所有测试数据的范围和特点如下表所示
请注意常数因子带来的程序效率上的影响。
对于 $100\%$ 的数据,保证:$1 \leq a_i,b_i \leq n$,$0 \leq t_i \leq 1000$,$1 \leq u_i,v_i \leq n$。
算法1
(树上差分+二分答案) $O(m \log n)$
由于答案有单调性,考虑二分$mid$的时间可以运输完吗
判定:
对于不改动时间>mid的路径,将路径上的边加一次
对于每条边,若每条不合法的路径都加了一次,那么如果不合法的最长路径-该边的长<=mid,判定成功
否则,判定失败
C++ 代码
#include <iostream>
#include <cstring>
using namespace std;
const int N = 300010, M = N * 2, K = 20;
inline int read()
{
int s = 0, w = 1;
char c = getchar();
for (; !isdigit(c); c = getchar()) if (c == '-') w = -1;
for (; isdigit(c); c = getchar()) s = (s << 3) + (s << 1) + (c ^ 48);
return s * w;
}
typedef long long LL;
struct Edge
{
int a, b, w;
}edge[N], query[N];
int n, m, p[N];
int h[N], e[M], w[M], ne[M], idx;
int d[N];
LL dist[N], res, q_dist[N];
int fa[N][K];
int f[N];
int q[N];
void add(int a, int b, int c)
{
e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++ ;
}
void bfs()
{
int hh = 0, tt = 0;
q[0] = 1;
d[1] = 1;
while (hh <= tt)
{
int t = q[hh ++ ];
for (int i = h[t]; ~i; i = ne[i])
{
int j = e[i];
if (d[j]) continue;
d[j] = d[t] + 1;
dist[j] = dist[t] + w[i];
fa[j][0] = t;
for (int k = 1; k <= 19; k ++ ) fa[j][k] = fa[fa[j][k - 1]][k - 1];
q[ ++ tt] = j;
}
}
}
int lca(int a, int b)
{
if (d[a] < d[b]) swap(a, b);
for (int i = 19; i >= 0; i -- )
if (d[fa[a][i]] >= d[b])
a = fa[a][i];
if (a == b) return a;
for (int i = 19; i >= 0; i -- )
if (fa[a][i] != fa[b][i])
a = fa[a][i], b = fa[b][i];
return fa[a][0];
}
void dfs(int u, int fa)
{
for (int i = h[u]; ~i; i = ne[i])
{
int j = e[i];
if (j == fa) continue;
dfs(j, u);
f[u] += f[j];
}
}
LL get_dist(int a, int b) //树上两点距离
{
return dist[a] + dist[b] - 2 * dist[lca(a, b)];
}
bool check(LL mid)
{
memset(f, 0, sizeof f);
int cnt = 0;
LL maxd = 0;
for (int i = 1; i <= m; i ++ )
{
int a = query[i].a, b = query[i].b;
if (q_dist[i] > mid)
{
cnt ++ ;
f[a] ++ , f[b] ++ , f[p[i]] -= 2; //树上差分
maxd = max(maxd, q_dist[i]);
}
}
dfs(1, -1);
for (int i = 1; i < n; i ++ )
{
int a = edge[i].a, b = edge[i].b;
if (d[a] < d[b]) swap(a, b);
if (f[a] == cnt && maxd - edge[i].w <= mid) return true;
}
return false;
}
int main()
{
memset(h, -1, sizeof h);
n = read(), m = read();
for (int i = 1; i < n; i ++ )
{
int a, b, c;
a = read(), b = read(), c = read();
add(a, b, c), add(b, a, c);
edge[i] = {a, b, c};
}
bfs(); //倍增LCA
for (int i = 1; i <= m; i ++ )
{
int a, b;
a = read(), b = read();
query[i] = {a, b};
q_dist[i] = get_dist(a, b);
p[i] = lca(a, b);
//printf("%lld\n", get_dist(a, b));
}
LL l = 0, r = 1e18;
while (l < r) //二分答案
{
LL mid = l + r >> 1;
if (check(mid)) r = mid;
else l = mid + 1;
}
printf("%lld\n", r);
return 0;
}
Orz
Orz