「NOIP2015提高组Day2T3」运输计划

题目描述

公元 2044 2044 2044年,人类进入了宇宙纪元。

L 国有 n n n个星球,还有 n − 1 n-1 n1 条双向航道,每条航道建立在两个星球之间,这$ n−1$ 条航道连通了 L 国的所有星球。

小 P 掌管一家物流公司, 该公司有很多个运输计划,每个运输计划形如:有一艘物流飞船需要从 u i u_i ui 号星球沿最快的宇航路径飞行到 v i v_i vi 号星球去。显然,飞船驶过一条航道是需要时间的,对于航道 j j j,任意飞船驶过它所花费的时间为 t j t_j tj,并且任意两艘飞船之间不会产生任何干扰。

为了鼓励科技创新, L 国国王同意小 P 的物流公司参与 L 国的航道建设,即允许小 P 把某一条航道改造成虫洞,飞船驶过虫洞不消耗时间。

在虫洞的建设完成前小 P 的物流公司就预接了 m m m 个运输计划。在虫洞建设完成后,这 m m m 个运输计划会同时开始,所有飞船一起出发。当这 m m m 个运输计划都完成时,小 P 的物流公司的阶段性工作就完成了。

如果小 P 可以自由选择将哪一条航道改造成虫洞, 试求出小 P 的物流公司完成阶段性工作所需要的最短时间是多少?

1 ≤ n , m ≤ 300000 1\le n,m\le 300000 1n,m300000

1 ≤ a i , b i , ≤ n 1\le a_i,b_i,\le n 1ai,bi,n, 0 ≤ t i ≤ 1000 0\le t_i\le1000 0ti1000, 1 ≤ u i , v i ≤ n 1\le u_i,v_i\le n 1ui,vin

题解

二分答案+LCA+树上差分

首先是树上路径问题,可以想到跟 LCA \text{LCA} LCA有关系

这里建议使用树链剖分求 LCA \text{LCA} LCA,倍增可能被卡

PS:讲树链剖分的一个优秀博客:树链剖分

求完 LCA \text{LCA} LCA的时候可以顺便统计路径长度: d i s = d e e p [ x ] + d e e p [ y ] − 2 × d e e p [ l c a ] dis=deep[x]+deep[y]-2\times deep[lca] dis=deep[x]+deep[y]2×deep[lca]

看到最大值最小,容易想到二分答案

可以证明如果 t 1 t1 t1的时间可以完成,那么 t 2 t2 t2也可以完成( t 2 > t 1 t2>t1 t2>t1

因此答案具有单调性,可以二分答案

那么现在就需要 c h e c k ( m i d ) check(mid) check(mid)

枚举路径长度比 m i d mid mid大的,用树上差分,给路径左右两端+1, l c a lca lca-2(边差分)

注意这里我们先将边权转移为点权,那么除了根节点每个点都有点权

然后如果找到一个点使得最长边减去这个点的点权(其实也是它与它父亲之间那条边的边权) > = m i d >=mid >=mid,且这些路径的长度都比 m i d mid mid大,就说明 m i d mid mid可行

否则如果找不到,就不可行

如果被卡了,可以采用 d f s dfs dfs序优化

Code

#include<cstdio>
#include<cstring>
#include<algorithm>
#define N 300005
using namespace std;
struct node
{
	int to,val,next,head;
}a[N<<1];
struct ques
{
	int x,y,lca,dis;
}edge[N];
int n,m,x,y,z,tot,cnt,L,R,f[N],deep[N],size[N],id[N],val[N],dis[N],son[N],top[N],cha[N];
void add(int x,int y,int z)
{
	a[++tot].to=y;
	a[tot].val=z;
	a[tot].next=a[x].head;
	a[x].head=tot;
}
void dfs1(int now,int fa)
{
	f[now]=fa;
	deep[now]=deep[fa]+1;
	size[now]=1;
	id[++cnt]=now;
	int maxson=-1;
	for (int i=a[now].head;i;i=a[i].next)
	{
		int v=a[i].to;
		if (v==fa) continue;
		val[v]=a[i].val;
		dis[v]=dis[now]+val[v];
		dfs1(v,now);
		size[now]+=size[v];
		if (size[v]>maxson) maxson=size[v],son[now]=v;
	}
}
void dfs2(int now,int topf)
{
	top[now]=topf;
	if (!son[now]) return;
	dfs2(son[now],topf);
	for (int i=a[now].head;i;i=a[i].next)
	{
		int v=a[i].to;
		if (v==son[now]||v==f[now]) continue;
		dfs2(v,v);
	}
}
int LCA(int x,int y)
{
	while (top[x]!=top[y])
	{
		if (deep[top[x]]<deep[top[y]]) swap(x,y);
		x=f[top[x]];
	}
	return deep[x]<=deep[y]?x:y;
}
//以上是树剖基操
bool check(int mid)
{
	int num=0;
	memset(cha,0,sizeof(cha));
	for (int i=1;i<=m;++i)
		if (edge[i].dis>mid)
		{
			cha[edge[i].x]++;
			cha[edge[i].y]++;
			cha[edge[i].lca]-=2;//(边差分)
			num++;//统计长度大于mid的路径个数
		}
	for (int i=n;i>=1;--i)
	{
		cha[f[id[i]]]+=cha[id[i]];//差分继承到父亲+dfs序优化
		if (val[id[i]]>=R-mid&&cha[id[i]]==num) return true;
	}
	return false;
}
int two_point(int left,int right)
{
	int medium,res=0;
	while (left<=right)
	{
		medium=(left+right)>>1;
		if (check(medium)) right=medium-1,res=medium;
		else left=medium+1;
	} 
	return res;
}
int main()
{
	freopen("transport.in","r",stdin);
	freopen("transport.out","w",stdout);
	scanf("%d%d",&n,&m);
	for (int i=1;i<n;++i)
	{
		scanf("%d%d%d",&x,&y,&z);
		add(x,y,z);
		add(y,x,z);
		L=max(L,z);//确定左边界
	}
	dfs1(1,0);
	dfs2(1,1);
	for (int i=1;i<=m;++i)
	{
		scanf("%d%d",&edge[i].x,&edge[i].y);
		edge[i].lca=LCA(edge[i].x,edge[i].y);
		edge[i].dis=dis[edge[i].x]+dis[edge[i].y]-dis[edge[i].lca]*2;
		R=max(R,edge[i].dis);//确定右边界
	}
	printf("%d\n",two_point(R-L,R+1));//二分答案
	fclose(stdin);
	fclose(stdout);
	return 0;
}

如果觉得我讲的有点不清楚,可以去参考这位大佬的博客

感谢阅读QAQ

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值