给你一颗(n)个节点带权的树以及(m)条路径端点,你可以将一条边的权值设为(0),要求使得操作后(m)条路径中的最长路径最短
这道题有很多解法,我在复习一种算法后更新一种
(Solution1)
二分+贪心+(LCA)+树上差分
这道题显然不是裸的树上差分,但需要树上差分进行(check)操作
首先我们预处理出(m)条路径的长度,这个是可以使用前缀和(O(n))求的,具体方法是在(dfs)过程中记录从根节点到(u)的距离(f[u]),求一条路径(u,v)的长度只需要
(dis[u]+[v]-2*dis[lca(u,v)])即可,画个图就很显然了,这是很重要的前缀和思想
看到题目中"使得最大距离最小"这句话,显然需要二分答案,我们二分操作后(将一条边权值归零)的最大距离,关键在于如何写(check)
考虑记录每一条长度比(mid)大的路径,用边差分记录一下,假设这样的路径有(cnt)条,那么显然这(cnt)条路径需要删除一条公共边的权值。
所以我们(dfs)一遍,对差分数组求前缀和
假设没有一条边被经过(cnt2)次,说明这(cnt)条路径没有公共边,显然不能通过删一条边来达到(mid),直接(return false);
若有多条边被经过(cnt2)次,贪心取最大的一条即可,因为删去的权值越大越能接近合法(显然)。
最后将(cnt)条路径长度的最大值减去找到的最大公共边,如果仍然(>mid)则(return false),否则(return true)
总结
1.在(dfs1)(预处理(lca))的过程中处理到根的距离
2.二分最大长度
3.对大于二分值的路径差分,(dfs2)寻找是否可以通过删去一条公共边使得最大长度(<mid)
时间复杂度(O((n+m)logn)),由于倍增(LCA)常数比较大,在当年(noip)是拿不到满分的,但是(luogu)数据比较水(AC)了
后面会介绍其他能通过此题(常数比较小)的算法
(Code1)
#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
#define re register
#define maxn 300010
using namespace std;
inline int read()
{
int x=0,f=1; char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
return x*f;
}
struct Edge{
int v,w,nxt;
}e[maxn<<2];
int maxx,cnt2,tmp2,es[maxn],guide;
int fr[maxn],to[maxn],num[maxn],sums2;
int a[maxn],z,x,y,cc[maxn];
int cf[maxn],ans,val[maxn],tmp,dep[maxn],head[maxn],cnt,dis[maxn];
int n,m,lg[maxn],num2,f[maxn][23];
inline void add(int u,int v,int w)
{
e[++cnt].v=v;
e[cnt].w=w;
e[cnt].nxt=head[u];
head[u]=cnt;
}
void pre()
{
for(re int i=1,num2=0;i<=n;i*=2) lg[i]=num2++;
for(re int i=1;i<=n;++i) if(!lg[i]) lg[i]=lg[i-1];
}
void dfs(int u,int fa,int w)
{;
dep[u]=dep[fa]+1;
f[u][0]=fa;
dis[u]=dis[fa]+w;//预处理
es[u]=w;//将边权固定到点上
for(int i=1;(1<<i)<=dep[u];++i)
f[u][i]=f[f[u][i-1]][i-1];
for(int i=head[u];i;i=e[i].nxt)
{
int ev=e[i].v;
if(ev!=fa) dfs(ev,u,e[i].w);
}
}
int lca(int x,int y)
{
if(dep[x]<dep[y]) swap(x,y);
for(re int i=lg[dep[x]-dep[y]];i>=0;--i)
{
if(dep[f[x][i]]<dep[y]) continue;
x=f[x][i];
}
if(x==y) return x;
for(re int i=lg[dep[x]-1];i>=0;--i)
{
if(f[x][i]==f[y][i]) continue;
x=f[x][i],y=f[y][i];
}
return f[x][0];
}
void dfs2(int u,int fa)
{
for(int i=head[u];i;i=e[i].nxt)
{
//int sums=0;
int ev=e[i].v;
if(ev==fa) continue;
dfs2(ev,u);
a[u]+=a[ev];
}
a[u]+=cf[u];//更新当前的前缀和
if(a[u]>=cnt2)
{
guide=max(guide,es[u]);
}
}
bool check(int x)//删去一条边后最大边是否<=mid
{
maxx=0,cnt2=0,tmp2=0;
memset(cf,0,sizeof(cf));
memset(a,0,sizeof(num));
for(re int i=1;i<=m;++i)
{
if(val[i]>x)
{
cf[fr[i]]++;
cf[to[i]]++;//边差分
cf[cc[i]]-=2;
cnt2++;
maxx=max(maxx,val[i]);//记录最大长度
}
}
guide=0;//guide表示最大公共边,guide=0表示没找到
dfs2(1,0);
if(!guide) return false;
if(maxx-guide>x) return false;
else return true;
}
int erfen()
{
int l=0,r=sums2;
while(l<=r)
{
int mid=(l+r)>>1;
if(check(mid)) r=mid-1;
else l=mid+1;
}
return l;
}
int main()
{
n=read(),m=read();
for(re int i=1;i<n;++i)
{
x=read(),y=read(),z=read();
add(x,y,z);
add(y,x,z);
sums2+=z;
}
for(re int i=1;i<=m;++i) fr[i]=read(),to[i]=read();
pre();
dfs(1,0,0);
for(re int i=1;i<=m;++i)
{
tmp=lca(fr[i],to[i]);
cc[i]=tmp;//记录一下最近公共祖先,常数优化
val[i]=dis[fr[i]]+dis[to[i]]-dis[tmp]*2;//前缀和O(1)计算
}
printf("%d
",erfen());
return 0;
}