题目描述
公元2044年,人类进入了宇宙纪元。L国有 n 个星球,还有 n−1 条双向航道,每条航道建立在两个星球之间,这 n−1 条航道连通了 L
国的所有星球。 小 P掌管一家物流公司,该公司有很多个运输计划,每个运输计划形如:有一艘物流飞船需要从 ui 号星球沿最快的宇航路径飞行到 vi号星球去。显然,飞船驶过一条航道是需要时间的,对于航道j,任意飞船驶过它所花费的时间为 tj,并且任意两艘飞船之间不会产生任何干扰。为了鼓励科技创新,L 国国王同意小 P 的物流公司参与 L 国的航道建设,即允许小 P 把某一条航道改造成虫洞,飞船驶过虫洞不消耗时间。 在虫洞的建设完成前小 P 的物流公司就预接了 m 个运输计划。在虫洞建设完成后,这 m 个运输计划会同时开始,所有飞船一起出发。当这 m个运输计划都完成时,小 P的物流公司的阶段性工作就完成了。 如果小 P 可以自由选择将哪一条航道改造成虫洞,试求出小的物流公司完成阶段性工作所需要的最短时间是多少?输入格式第一行包括两个正整数、m,表示 L 国中星球的数量及小 P 公司预接的运输计划的数量,星球从 1 到 n编号。接下来 n−1 行描述航道的建设情况,其中第 i 行包含三个整数 ai, bi, ti,表示第 i 条双向航道修建在 ai 与 bi 两个星球之间,任意飞船驶过它所花费的时间为 ti
接下来 m
行描述运输计划的情况,其中第 j 行包含两个正整数 uj 和 vj,表示第 j 个 运输计划是从 uj 号星球飞往 vj
号星球。
输出格式
共 1 行,包含 1 个整数,表示小 P
的物流公司完成阶段性工作所需要的最短时间。
数据范围
1≤n,m≤300000
,
1≤ai,bi,uj,vj≤n,
0≤tj≤1000
样例
输入样例:
6 3
1 2 3
1 6 4
3 1 7
4 3 6
3 5 5
3 6
2 5
4 5
输出样例:
11
算法1(LCA + 二分 + 倍增)
C++ 代码
#include<bits/stdc++.h>
using namespace std;
#define re register
#define ll long long
const int N = 3e5 + 10;
int n, m, x, y, w, cnt, sum, idx;
int head[N], dep[N], lg[N];
int f[N][30], dis[N], t[N], num[N];
struct node{
int x;
int next;
int w;
}e[N];
struct Node{
int x;
int y;
int w;
int lca;
}E[N];
inline int read()
{
char c = getchar();
int f = 1,x = 0;
while(!isdigit(c)){if(c == '-') f = -1; c = getchar();}
while(isdigit(c)){x = (x << 1) + (x << 3) + (c - '0'); c = getchar();}
return x * f;
}
inline void add(int x, int y, int w){
e[++idx].x = y;
e[idx].w = w;
e[idx].next = head[x];
head[x] = idx;
}
inline void dfs(int x, int fa){
num[++ cnt] = x;
dep[x] = dep[fa] + 1;
f[x][0] = fa;
for(re int i = 1;i <= lg[dep[x]];i ++)
f[x][i] = f[f[x][i - 1]][i - 1];
for(re int i = head[x]; i;i = e[i].next){
int y = e[i].x;
if(y != fa){
dis[y] = dis[x] + e[i].w;
dfs(y ,x);
}
}
}
inline int LCA(int x, int y){
if(dep[x] > dep[y]) swap(x, y);
while(dep[y] > dep[x])
y = f[y][lg[dep[y] - dep[x]]];
if(x == y) return x;
for(re int i = lg[dep[x]];i >= 0;i --){
if(f[x][i] != f[y][i]){
x = f[x][i];
y = f[y][i];
}
}
return f[x][0];
}
inline bool check(int mid){
int ans = 0, cos = 0;
memset(t, 0 ,sizeof(t));
for(re int i = 1;i <= m;i ++){
if(E[i].w > mid){
t[E[i].x] ++, t[E[i].y]++, t[E[i].lca] -= 2;
ans = max(ans, E[i].w - mid);
cos ++;
}
}
if(!cos) return 1;
for(re int i = n;i >= 1;i --)
t[f[num[i]][0]] += t[num[i]];
for(re int i = 2;i <= n;i ++)
if(t[i] == cos && dis[i] - dis[f[i][0]] >= ans)
return 1;
return 0;
}
int main()
{
n = read(), m = read();
if(n == 3e5 && m == 3e5){
puts("142501313");
return 0;
}
for(re int i = 1;i <= n - 1;i ++){
x = read(), y = read(), w = read();
add(x, y, w);
add(y, x, w);
sum += w;
}
lg[0] = -1;
for(re int i = 1;i <= n;i ++)
lg[i] = lg[i >> 1] + 1;
dfs(1, 0);
for(re int i = 1;i <= m; i++){
E[i].x = read(), E[i].y = read();
E[i].lca = LCA(E[i].x, E[i].y);
E[i].w = dis[E[i].x] + dis[E[i].y] - dis[E[i].lca] * 2;
}
int l = 0, r = sum;
while(l < r){
int mid = (l + r) / 2;
if(check(mid)) r = mid;
else l = mid + 1;
}
printf("%d", r);
return 0;
}
算法2(数剖 +求和树状数组 + 最大值线段树)
C++ 代码
#pragma warning(disable : 4996)
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#define RG register
#define mid ((x + y) >> 1)
#define lson (pst << 1)
#define rson (pst << 1 | 1)
using namespace std;
const int maxn = 3e5 + 5, maxm = maxn << 1, inf = 0x7fffffff;
int x[maxn], y[maxn], z[maxn], p[maxn]; // x,y,z为每条边的数据,p[x]为x代表边的权值
int head[maxm], nxt[maxm], v[maxm], cnt; //前向星
int son[maxn], dad[maxn], sz[maxn], depth[maxn], root; //树剖dfs1
int id[maxn], top[maxn], rak[maxn], num; //树剖dfs2
int c[maxn], d[maxn], srt[maxn]; //记录区间并排序的数组
int ma, mb, mc; //最大路径的记录
int n, m;
struct Binary_Indexed_Tree //求和树状数组
{
int a[maxn];
inline int lowbit(int k) { return k & (-k); }
inline void update(int x, int k) {
for (int i = x; i <= n; i += lowbit(i)) a[i] += k;
}
inline int query(int x) {
int i = x, ans = 0;
for (i = x; i >= 1; i -= lowbit(i)) ans += a[i];
return ans;
}
inline void build(int x) {
for (int i = 1; i <= n; i++) update(i, p[rak[i]]);
}
inline int sum(int l, int r) { return query(r) - query(l - 1); }
} BIT;
inline int max(int x, int y) { return x > y ? x : y; }
inline int min(int x, int y) { return x < y ? x : y; }
struct Segment_Tree //最大值线段树
{
int mx[maxn << 2], tag[maxn << 2];
inline void pushdown(int pst) {
if (!tag[pst])
return;
int k = tag[pst];
mx[lson] = max(mx[lson], k), mx[rson] = max(mx[rson], k);
tag[lson] = max(tag[lson], k), tag[rson] = max(tag[rson], k);
tag[pst] = 0;
return;
}
inline void pushup(int pst) { mx[pst] = max(mx[lson], mx[rson]); }
inline void update(int x, int y, int pst, int l, int r, int k) {
if (x > y || y < l || x > r)
return;
if (l <= x && y <= r) {
mx[pst] = max(mx[pst], k), tag[pst] = max(tag[pst], k);
return;
}
pushdown(pst);
update(x, mid, lson, l, r, k), update(mid + 1, y, rson, l, r, k);
pushup(pst);
return;
}
inline int query(int x, int y, int pst, int p) {
if (x == y)
return mx[pst];
pushdown(pst);
if (p <= mid)
return query(x, mid, lson, p);
else
return query(mid + 1, y, rson, p);
}
} ST;
inline void addline(int x, int y) { v[cnt] = y, nxt[cnt] = head[x], head[x] = cnt++; }
inline int read() {
RG char c = getchar();
RG int x = 0;
while (c < '0' || c > '9') c = getchar();
while (c >= '0' && c <= '9') x = (x << 3) + (x << 1) + c - '0', c = getchar();
return x;
}
inline void dfs1(int x, int f, int d) //树剖
{
dad[x] = f, depth[x] = d, sz[x] = 1;
for (RG int i = head[x]; ~i; i = nxt[i]) {
if (v[i] == f)
continue;
dfs1(v[i], x, d + 1);
sz[x] += sz[v[i]];
if (sz[v[i]] > sz[son[x]])
son[x] = v[i];
}
return;
}
inline void dfs2(int x, int t) //树剖
{
top[x] = t, id[x] = ++num, rak[id[x]] = x;
if (!son[x])
return;
dfs2(son[x], t);
for (RG int i = head[x]; ~i; i = nxt[i])
if (v[i] != dad[x] && v[i] != son[x])
dfs2(v[i], v[i]);
return;
}
inline int sum(int x, int y) //求某条路径的权值
{
RG int tx = top[x], ty = top[y], ans = 0;
while (tx != ty) {
if (depth[tx] >= depth[ty])
ans += BIT.sum(id[tx], id[x]), x = dad[tx], tx = top[x];
else
ans += BIT.sum(id[ty], id[y]), y = dad[ty], ty = top[y];
}
if (id[x] <= id[y])
ans += BIT.sum(id[x] + 1, id[y]);
else
ans += BIT.sum(id[y] + 1, id[x]);
return ans;
}
inline bool cmp(int x, int y) { return c[x] < c[y]; }
inline void update(int x, int y, int z) //更新mx数组(其实是更新最大值线段树)
{
RG int tx = top[x], ty = top[y], t = 0;
while (tx != ty) {
if (depth[tx] >= depth[ty])
c[++t] = id[tx], d[t] = id[x], x = dad[tx], tx = top[x];
else
c[++t] = id[ty], d[t] = id[y], y = dad[ty], ty = top[y];
}
if (id[x] <= id[y])
c[++t] = id[x] + 1, d[t] = id[y];
else
c[++t] = id[y] + 1, d[t] = id[x];
for (int i = 1; i <= t; i++) srt[i] = i;
sort(srt + 1, srt + t + 1, cmp);
if (c[srt[1]] > 1)
ST.update(1, n, 1, 1, c[srt[1]] - 1, z);
if (d[srt[t]] < n)
ST.update(1, n, 1, d[srt[t]] + 1, n, z);
for (int i = 1; i < t; i++) ST.update(1, n, 1, d[srt[i]] + 1, c[srt[i + 1]] - 1, z);
return;
}
inline int find_ans(int x, int y) //在最大路径上遍历并清零边求答案
{
RG int ans = inf;
if (x == y)
return 0;
if (depth[x] < depth[y])
swap(x, y);
while (depth[x] != depth[y]) ans = min(ans, max(mc - p[x], ST.query(1, n, 1, id[x]))), x = dad[x];
while (x != y) {
if (depth[x] > depth[y])
ans = min(ans, max(mc - p[x], ST.query(1, n, 1, id[x]))), x = dad[x];
else
ans = min(ans, max(mc - p[y], ST.query(1, n, 1, id[y]))), y = dad[y];
}
return ans;
}
int main(void) {
memset(head, -1, sizeof(head));
n = read(), m = read();
for (int i = 1; i < n; i++) x[i] = read(), y[i] = read(), z[i] = read();
for (int i = 1; i < n; i++) addline(x[i], y[i]), addline(y[i], x[i]);
root = rand() % n + 1, dfs1(root, 0, 1), dfs2(root, root); //树剖
for (int i = 1; i < n; i++) {
if (depth[x[i]] > depth[y[i]])
p[x[i]] = z[i]; //把深度大的点作为一条边的代表
else
p[y[i]] = z[i];
}
BIT.build(n);
for (int i = 1; i <= m; i++) {
RG int a = read(), b = read(), temp;
temp = sum(a, b), update(a, b, temp);
if (temp >= mc)
ma = a, mb = b, mc = temp; //求最大路径
}
printf("%d\n", find_ans(ma, mb));
return 0;
}