正题
题目链接:https://www.luogu.org/problemnew/show/P2680
题目大意
一棵带权无根树,给出若干条路径。选择一条边使其边权变为0,要求路径的长度的最大值最小。
解题思路
首先最大值最小我们可以想到二分答案,现在我们二分到midmidmid了,我们如何判断是否满足。
我们我们发现只需要管路径长度大于midmidmid的,假设有numnumnum条,那么我们必须找到一条边满足:
- 这numnumnum条路径都经过这个点
- 若这个点的边权为www,那么要求满足最长的路径长度maxlen−w≤midmaxlen-w\leq midmaxlen−w≤mid
首先我们用LCALCALCA可以计算出每条路径的原始长度,然后我们可以用树上差分有多少条路径经过一个点。
时间复杂度O(nlogn)O(n\log n)O(nlogn)
OverOverOver
codecodecode
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<queue>
using namespace std;
const int N=300100;
struct edge_node{int to,next,w;
}a[N*2];
int n,m,tot,t,num,del,maxs;
int ls[N],dep[N],dis[N],f[N][30];
int lca[N],dx[N],dy[N],len[N],acr[N];
queue<int> q;
void addl(int x,int y,int w)
{a[++tot].to=y;a[tot].next=ls[x];a[tot].w=w;ls[x]=tot;
}
void bfs()
{t=(int)(log(n)/log(2))+1;q.push(1);dep[1]=1;while(!q.empty()){int x=q.front();q.pop();for(int i=ls[x];i;i=a[i].next){int y=a[i].to;if(dep[y]) continue;dep[y]=dep[x]+1;f[y][0]=x;dis[y]=dis[x]+a[i].w;q.push(y);}}for(int i=1;i<=t;i++)for(int j=1;j<=n;j++)f[j][i]=f[f[j][i-1]][i-1];
}
int LCA(int x,int y)
{if(dep[x]>dep[y]) swap(x,y);for(int i=t;i>=0;i--)if(dep[f[y][i]]>=dep[x])y=f[y][i];if(x==y) return x;for(int i=t;i>=0;i--)if(f[y][i]!=f[x][i])y=f[y][i],x=f[x][i];return f[x][0];
}
void dfs(int x,int fa,int rec)
{for(int i=ls[x];i;i=a[i].next){int y=a[i].to;if(y==fa) continue;dfs(y,x,i);acr[x]+=acr[y]; }if(acr[x]==num)del=max(del,a[rec].w);
}
int main()
{freopen("testdata.in","r",stdin);scanf("%d%d",&n,&m);for(int i=1;i<n;i++){int x,y,w;scanf("%d%d%d",&x,&y,&w);addl(x,y,w);addl(y,x,w);}bfs();int l=0,r=N*1000;for(int i=1;i<=m;i++){scanf("%d%d",&dx[i],&dy[i]);lca[i]=LCA(dx[i],dy[i]);len[i]=dis[dx[i]]+dis[dy[i]]-dis[lca[i]]*2;maxs=max(maxs,len[i]);}while(l<=r){int mid=(l+r)/2;memset(acr,0,sizeof(acr));num=0;del=0;for(int i=1;i<=m;i++)if(len[i]>mid)acr[dx[i]]++,acr[dy[i]]++,acr[lca[i]]-=2,num++;dfs(1,1,0);if(maxs-del<=mid) r=mid-1;else l=mid+1;}printf("%d",l);
}