题目传送门
题意解析:题目给了你有n个点的一棵树,然后给了你m个询问,询问两个点之间的距离。你可以让一条边的长度变成0,并且这条边是对于所有的询问都是一样的,求最后使一条边变成0后,询问的答案的最大值最小。
My opinion:这题目因为是一棵树,我们就可以预处理出每组询问在没有去边之前的答案,可以用lca求出两个点的最近公共祖先,再用u->v的距离=dis[u]+dis[v]-2*dis[p](dis[x]是表示点x到根的距离,p是u和v的最近公共祖先)预处理出答案,时间复杂度为O(m*lg(n))(如果用st表求lca的话)。之后就是去掉哪一条边的问题了,一开始的想法就是枚举去掉的边,然后对于每组询问求解是否有这条边,然后去掉,之后……就完美的TLE了。该怎么办呢?按照一dalao的说法,我们可以将这个求解性问题转换成判断性问题,使用二分答案法来求答案,然后判断。
总结:
1、用lca求出每组询问的答案。
2、二分答案。
3、判断是否可行。
4、输出答案。
判断方法为:
对于答案ans,记录距离>ans的点对个数s1和最大差值为s2,对于每条不合法的路径,维护每个点的经过次数,然后枚举点,如果经过次数=s1,就尝试将它建成虫洞,如果它对应的边的权值>=s2,那么ans就合法。如果所有点都不行,则ans不合法。
关键是统计每个点在非法路径中的经过次数。我们可以维护sum数组,对于每条非法的路径(u,v),sum[u]++,sum[v]++,sum[lca(u,v)]-=2,这样最后sum[u]+=sum[u的所有儿子],就实现了每个点的经过次数的统计。
判断方法转自某dalao的博客
相比起来超长的代码:
#include<iostream>
#include<cmath>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define rep(i,a,n) for (int i=a;i<=n;i++)
#define per(i,a,n) for (int i=a;i>=n;i--)
#define Clear(a,x) memset(a,x,sizeof(a))
#define ll long long
#define INF 2000000000
#define eps 1e-8
using namespace std;
int read(){
int x=0,f=1;
char ch=getchar();
while (ch<'0'||ch>'9') f=ch=='-'?-1:f,ch=getchar();
while (ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar();
return x*f;
}
const int maxn=300005;
int n,m,len,Max_road;
bool flag[maxn];
int head[maxn],vet[maxn<<1],Next[maxn<<1],road[maxn<<1];
int size[maxn],dep[maxn],father[maxn];
int l[maxn],r[maxn],p[maxn],dis[maxn],s[maxn];
int fa[maxn][20];
void add(int u,int v,int w){
vet[++len]=v;
Next[len]=head[u];
head[u]=len;
road[len]=w;
}
void dfs(int u){
flag[u]=0;
int len=1;
while ((1<<len)<size[u]){
fa[u][len]=fa[fa[u][len-1]][len-1];
len++;
}
for (int e=head[u];e;e=Next[e]){
int v=vet[e];
if (v!=fa[u][0]){
dep[v]=dep[u]+road[e];
size[v]=size[u]+1;
fa[v][0]=u;
father[v]=road[e];
dfs(v);
}
}
}
void DFS(int u){
flag[u]=0;
for (int e=head[u];e;e=Next[e]){
int v=vet[e];
if (flag[v]){
DFS(v);
s[u]+=s[v];
}
}
}
int lca(int l,int r){
if (size[l]<size[r]) swap(l,r);
per(i,19,0)
if (size[l]-size[r]>=(1<<i)) l=fa[l][i];
if (l==r) return l;
per(i,19,0)
if (fa[l][i]!=fa[r][i]){
l=fa[l][i];
r=fa[r][i];
}
return fa[l][0];
}
bool check(int mid){
Clear(s,0);
Clear(flag,1);
int ans=0,max1=0,max2=0;
rep(i,1,m){
if (dis[i]<=mid) continue;
s[l[i]]++,s[r[i]]++;
s[p[i]]-=2;
ans++;
max1=max(max1,dis[i]);
}
DFS(1);
rep(i,1,n)
if (s[i]==ans) max2=max(max2,father[i]);
if (max1-max2<=mid) return false;
else return true;
}
int main(){
n=read(),m=read();
rep(i,1,n-1){
int u=read(),v=read(),w=read();
add(u,v,w);
add(v,u,w);
}
Clear(flag,1);
dfs(1);
rep(i,1,m){
l[i]=read(),r[i]=read();
int x=lca(l[i],r[i]);
p[i]=x;
dis[i]=dep[l[i]]+dep[r[i]]-2*dep[x];
Max_road=max(Max_road,dis[i]);
}
int L=0,R=Max_road;
while (L<R){
int mid=(L+R)>>1;
if (check(mid)) L=mid+1;
else R=mid;
}
printf("%d\n",R);
return 0;
}