题目描述
公元 2044 2044 2044年,人类进入了宇宙纪元。
L 国有 n n n个星球,还有 n − 1 n-1 n−1 条双向航道,每条航道建立在两个星球之间,这$ 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 1≤n,m≤300000
1 ≤ a i , b i , ≤ n 1\le a_i,b_i,\le n 1≤ai,bi,≤n, 0 ≤ t i ≤ 1000 0\le t_i\le1000 0≤ti≤1000, 1 ≤ u i , v i ≤ n 1\le u_i,v_i\le n 1≤ui,vi≤n
题解
二分答案+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