题解- P2680 运输计划
-
题目意思
就是在一棵树中使得一条边的边权为 0 0 0,使得 m m m条航线的最长链最短。
-
S o l Sol Sol
一道古代题,也是一道套路题。
要求最长链最短我们显然能够想到二分答案。但是关键在于如何判断答案是正确的。
我们在这里设二分的答案为 m i d mid mid, m m m条航线的长度为 d i d_i di。如果 d i ≤ m i d d_i \leq mid di≤mid显然不需要考虑,剩下的就是那几条 m i d mid mid小于 d i d_i di的了。
此时我们会
很自然的想到树上差分,那如何运用呢?我们设有 s s s条航线的 m i d mid mid小于 d i d_i di,那么我们要想方设法地去删掉那条 s s s条公共的最长的边。此时差分就呼之欲出了,我们对于这 s s s条链都做一次差分(两个端点 u , v u,v u,v各自加一, l c a ( u , v ) lca(u,v) lca(u,v)减二)这是边差分的基本操作。这样我们只要 d f s dfs dfs一下,就可以判断哪条路径被经过了 s s s次,我们还要做出一个判断就是最长不满足要求的航线减去这条被删掉的边是否能满足要求。于是这道题目就做完了。。
#include <bits/stdc++.h>
using namespace std;
inline int read()
{
int sum=0,ff=1; char ch=getchar();
while(!isdigit(ch))
{
if(ch=='-') ff=-1;
ch=getchar();
}
while(isdigit(ch))
sum=sum*10+(ch^48),ch=getchar();
return sum*ff;
}
const int N=3e5+5;
int n,m,cnt,head[N],f[N][26];
int gs[N],d[N],dep[N],p[N],ans,mx;
struct nood
{
int nex,to,w;
};
nood e[N*4];
struct que
{
int u,v,dis,lca;
};
que c[N];
inline void jia(int u,int v,int w)
{
e[++cnt].nex=head[u];
head[u]=cnt;
e[cnt].to=v;
e[cnt].w=w;
}
inline void dfs(int u,int fa,int w)
{
p[u]=w;
for ( int i=1;i<=21;i++ ) f[u][i]=f[f[u][i-1]][i-1];
for ( int i=head[u];i;i=e[i].nex )
{
int v=e[i].to;
if(v==fa) continue;
dep[v]=dep[u]+1;
f[v][0]=u;
d[v]=d[u]+e[i].w;
dfs(v,u,e[i].w);
}
}
inline int LCA(int x,int y)
{
if(dep[x]>dep[y]) swap(x,y);
for ( int i=20;i>=0;i-- )
if(dep[y]-(1<<i)>=dep[x]) y=f[y][i];
if(x==y) return x;
for ( int i=20;i>=0;i-- )
if(f[x][i]!=f[y][i])
{
x=f[x][i];
y=f[y][i];
}
return f[x][0];
}
inline void dfs2(int u,int fa)
{
for ( int i=head[u];i;i=e[i].nex )
{
int v=e[i].to;
if(v==fa) continue;
dfs2(v,u);
gs[u]+=gs[v];
}
}
inline bool pd(int mid)
{
int num=0;
memset(gs,0,sizeof(gs));
for ( int i=1;i<=m;i++ )
if(c[i].dis>mid)
{
num++;
int u=c[i].u;
int v=c[i].v;
int z=c[i].lca;
gs[u]++;
gs[v]++;
gs[z]-=2;
}
//树上差分
if(!num) return true;
dfs2(1,0);
//dfs一遍找出经过次数等于num的边
for ( int i=1;i<=n;i++ )
if(gs[i]>=num&&mx-mid<=p[i]) return true;
return false;
}
int main()
{
n=read();
m=read();
for ( int i=1;i<n;i++ )
{
int u,v,w;
u=read();
v=read();
w=read();
jia(u,v,w);
jia(v,u,w);
}
dfs(1,0,0);
//lca预处理
for ( int i=1;i<=m;i++ )
{
int u,v;
u=read();
v=read();
c[i].u=u;
c[i].v=v;
c[i].lca=LCA(u,v);
c[i].dis=d[u]+d[v]-(d[c[i].lca]<<1);
//求树上距离
mx=max(mx,c[i].dis);
//找出最长航道
}
int l=0,r=mx;
while(l<=r)
{
int mid=(l+r)/2;
if(pd(mid))
{
ans=mid;
r=mid-1;
}
else l=mid+1;
}//二分
printf("%d\n",ans);
return 0;
}