运输计划(树上差分+二分答案+树上前缀和)

3 篇文章 0 订阅
2 篇文章 0 订阅

题目描述

公元 2044 2044 2044 年,人类进入了宇宙纪元。
L L L 国有 n n n 个星球,还有 n − 1 n−1 n1 条双向航道,每条航道建立在两个星球之间,这 n − 1 n - 1 n1 条航道连通了 L L L 国的所有星球。
P P P 掌管一家物流公司,该公司有很多个运输计划,每个运输计划形>如:有一艘物流飞船需要从 u i u_i ui 号星球沿最快的宇航路径飞行到 v i v_i vi 号星球去。显然,飞船驶过一条航道是需要时间的,对于航道 j j j,任意飞船驶过它所花费的时间为 t j t_j tj​​ ,并且任意两艘飞船之间不会产生任何干扰。
为了鼓励科技创新, L L L 国国王同意小 P P P 的物流公司参与 L L L 国的航道建设,即允许小 P P P 把某一条航道改造成虫洞,飞船驶过虫洞不消耗时间。
在虫洞的建设完成前小 P P P 的物流公司就预接了 m m m 个运输计划。在虫洞建设完成后,这 m m m 个运输计划会同时开始,所有飞船一起出发。当这 m m m 个运输计划都完成时,小 P P P 的物流公司的阶段性工作就完成了。
如果小 P P P 可以自由选择将哪一条航道改造成虫洞,试求出小 P P P 的物流公司完成阶段性工作所需要的最短时间是多少?

输入格式

第一行包括两个正整数 n n n m m m,表示 L L L 国中星球的数量及小 P P P 公司预接的运输计划的数量,星球从 1 1 1 n n n 编号。
接下来 n − 1 n - 1 n1 行描述航道的建设情况,其中第 i i i 行包含三个整数 a i a_i ai​​ 、 b i b_i bi​ 和 t i t_i ti​ ,表示第 i i i 条双向航道修建在 a i a_i ai b i b_i bi 两个星球之间,任意飞船驶过它所花费的时间为 t i t_i ti​ 。 接下来 m m m 行描述运输计划的情况,其中第 j j j 行包含两个正整数 u j u_j uj​ 和 v j v_j vj​,表示第 j j j 个运输计划是从 u j u_j uj​ 号星球飞往 v j v_j vj 号星球。

输出格式

1 1 1 行,包含 1 1 1 个整数,表示小 P P P 的物流公司完成阶段性工作所需要的最短时间。

样例输入

6 6 6 3 3 3
1 1 1 2 2 2 3 3 3
1 1 1 6 6 6 4 4 4
3 3 3 1 1 1 7 7 7
4 4 4 3 3 3 6 6 6
3 3 3 5 5 5 5 5 5
3 3 3 6 6 6
2 2 2 5 5 5
4 4 4 5 5 5

样例输出

11 11 11

数据范围与提示

对于 100 100 100% 的数据, 100 ≤ n ≤ 300000 , 1 ≤ m ≤ 300000 100≤n≤300000,1≤m≤300000 100n3000001m300000


解析

我们会发现题目让我们求的是清空一条边的边权后 m m m条路径中最长路径的最小值。
引用某老师的说法, 这种题目99%都是二分答案嘛。


证明答案的二分性:

如果答案为 x x x可以,那么比 x x x大的答案都是合法的。
如果答案为 x x x不行,那么比 x x x小的答案都是非法的。


那么只要二分这个答案并且在 O ( n ) O(n) O(n)的时间内检验即可。
如果有 c n t cnt cnt条路径的长度 > m i d >mid >mid,那么去掉的边必须是这 c n t cnt cnt条边的并集。


证明:

如果去掉的边不是这 c n t cnt cnt条边的并集。那么一定存在一条路径 s s s的路径长度任然 > m i d >mid >mid,使得答案不成立。


那么我们可以得出答案 m i d mid mid成立条件的结论:
存在一条边 e e e使得所有的长度超过 m i d mid mid的路径都经过 e e e,且这些路径中的最大长度 m a x l e n maxlen maxlen满足 m a x l e n − v a l e ≤ m i d maxlen-val_e≤mid maxlenvalemid


具体操作:

  1. 将边权转换成点权,已 1 1 1为根进行 d f s dfs dfs后将边权置于深度较深的点。
  2. 求出从 1 1 1到每个点 u u u的距离 d u d_u du,这样树上任意两点 u , v u,v u,v间的距离 = d u + d v − 2 ∗ d l c a ( u , v ) =d_u+d_v-2*d_{lca(u,v)} =du+dv2dlca(u,v)
  3. 树上差分统计经过边 e e e的路径个数时,只需要在 u u u v v v处各 + 1 +1 +1,在 L C A ( u , v ) LCA(u,v) LCA(u,v) − 2 -2 2

代码

#include<cstdio>
using namespace std;
const int maxn = 300005;
const int maxe = 600010;
const int maxm = 300005;
const int oo = 1000000000;
int n , m;
int edgenum;
int Next[maxe] , vet[maxe] , val[maxe] , head[maxn];
int w[maxn] , d[maxn];
int dep[maxn] , f[maxn][20];
int cnt[maxn] , sum[maxn];
struct path{int u , v , lca , dis;}rd[maxm];
void swap(int &x , int &y){x ^= y , y ^= x , x ^= y;}
int max(int x , int y){return x > y ? x : y;}
int min(int x , int y){return x < y ? x : y;}
int read()
{
	char ch = getchar();
	while(ch < '0' || ch > '9') ch = getchar();
	int res = 0;
	while(ch >= '0' && ch <= '9') res = (res << 3) + (res << 1) + (ch ^ 48) , ch = getchar();
	return res;
}
void add_edge(int u , int v , int cost)
{
	Next[++edgenum] = head[u];
	vet[edgenum] = v;
	val[edgenum] = cost;
	head[u] = edgenum;
}
void dfs(int u , int fa)
{
	dep[u] = dep[fa] + 1;
	for(int i = 1;i <= 18;++i) f[u][i] = f[f[u][i - 1]][i - 1];
	for(int e = head[u];e;e = Next[e])
	{
		int v = vet[e];
		if(v == fa) continue;
		d[v] = d[u] + val[e];
		w[v] = val[e];
		f[v][0] = u;
		dfs(v , u);
	}
}
int LCA(int u , int v)
{
	if(dep[u] < dep[v]) swap(u , v);
	for(int i = 18;i >= 0;--i)
	{
		if(dep[f[u][i]] >= dep[v]) u = f[u][i];
		if(u == v) return u;
	}
	for(int i = 18;i >= 0;--i)
		if(f[u][i] != f[v][i]) u = f[u][i] , v = f[v][i];
	return f[u][0];
}
int dis(int u , int v){return d[u] + d[v] - (d[LCA(u , v)] << 1);}
void add(int u , int v , int lca){cnt[u]++ , cnt[v]++ , cnt[lca] -= 2;}
void calc(int u , int fa)
{
	sum[u] = cnt[u];
	for(int e = head[u];e;e = Next[e])
	{
		int v = vet[e];
		if(v == fa) continue;
		calc(v , u);
		sum[u] += sum[v];
	}
}
bool check(int dist)
{
	for(int i = 1;i <= n;++i) cnt[i] = 0;
	int num = 0 , max_dis = 0;
	for(int i = 1;i <= m;++i)
		if(rd[i].dis > dist)
		{
			add(rd[i].u , rd[i].v , rd[i].lca);
			num++;
			max_dis = max(max_dis , rd[i].dis);
		}
	calc(1 , 0);
	for(int i = 1;i <= n;++i)
		if(sum[i] == num && max_dis - w[i] <= dist) return 1;
	return 0;
}
int main()
{
	n = read() , m = read();
	for(int i = 1;i < n;++i)
	{
		int u = read() , v = read() , cost = read();
		add_edge(u , v , cost);
		add_edge(v , u , cost);
	}
	dfs(1 , 0);
	int r = 0;
	for(int i = 1;i <= m;++i)
	{
		int u = read() , v = read() , dist = dis(u , v);
		rd[i] = (path){u , v , LCA(u , v) , dist};
		r = max(r , dist);
	}
	int l = 0;
	while(l < r)
	{
		int mid = (l + r - 1) >> 1;
		if(check(mid)) r = mid;
		else l = mid + 1;
	}
	printf("%d\n",r);
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值