大致题意: 一棵树,每条边有一个边权,你可以选择其中一个修改为(0)。给定若干条树上路径,求最小化最长的路径长度。
前言
果然数据结构题做傻掉了,一开始竟然想出一个二维数点的做法 emmm(过应该是可以过的,但可能要稍微麻烦一些)
无所畏惧两个(log),洛谷上最慢的点跑了(1.96s)。。。其实完全可以写(O(1)LCA)做到单(log),但懒得去写了,倍增它不香吗。。。
二分
考虑我们二分答案(x),那么对于每条长度大于(x)的路径(路径长度可以预处理),都需要修改一条该路径上的边使它长度小于等于(x)。
换言之,修改的路径必须属于所有非法路径的交集,那么必然贪心地选择交集中的最大边权。
而如果最长的路径长度减去最大边权小于等于(x),就说明(x)是个可行答案,否则不可行。
那么问题来了,我们该如何求出树上若干路径的交集?
差分
这个套路其实我在另一道题中做过:【BZOJ4424】Fairy(树上差分)。
我们求解每条边被经过的次数,把这一信息维护在它连接的子节点上。
每次给(x,y)的点权分别加(1),给(LCA(x,y))的点权减(2),然后(dfs)一遍统计子树和即可得到每条边被经过的次数。
如果一条边被经过的次数等于非法路径总数,就说明它是交集中的边,否则不是。
这样一来这道题就做完了。
代码
#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 300000
#define LN 20
#define add(x,y,v) (e[++ee].nxt=lnk[x],e[lnk[x]=ee].to=y,e[ee].val=v)
using namespace std;
int n,m,Mx,a[N+5],b[N+5],p[N+5],g[N+5],v[N+5],ee,lnk[N+5];
struct edge {int to,nxt,val;}e[N<<1];
class FastIO
{
private:
#define FS 100000
#define tc() (A==B&&(B=(A=FI)+fread(FI,1,FS,stdin),A==B)?EOF:*A++)
#define D isdigit(c=tc())
char c,*A,*B,FI[FS];
public:
I FastIO() {A=B=FI;}
Tp I void read(Ty& x) {x=0;W(!D);W(x=(x<<3)+(x<<1)+(c&15),D);}
Ts I void read(Ty& x,Ar&... y) {read(x),read(y...);}
#undef D
}F;
class MultiLCA
{
private:
int d[N+5],D[N+5],f[N+5][LN+5];
public:
I void Init(CI x=1)//初始化
{
RI i;for(i=1;i<=LN;++i) f[x][i]=f[f[x][i-1]][i-1];
for(i=lnk[x];i;i=e[i].nxt) e[i].to^f[x][0]&&(D[e[i].to]=D[x]+e[i].val,
v[e[i].to]=e[i].val,d[e[i].to]=d[f[e[i].to][0]=x]+1,Init(e[i].to),0);
}
I int LCA(RI x,RI y)//倍增LCA
{
RI i;d[x]<d[y]&&(x^=y^=x^=y);
for(i=0;d[x]^d[y];++i) (d[x]^d[y])>>i&1&&(x=f[x][i]);if(x==y) return x;
for(i=LN;~i;--i) f[x][i]^f[y][i]&&(x=f[x][i],y=f[y][i]);return f[x][0];
}
I int Dis(CI x,CI y) {return D[x]+D[y]-(D[LCA(x,y)]<<1);}//询问树上距离
}T;
I void dfs(CI x=1,CI lst=0)//推标记
{
for(RI i=lnk[x];i;i=e[i].nxt) e[i].to^lst&&(dfs(e[i].to,x),g[x]+=g[e[i].to]);
}
I bool Check(CI x)//验证
{
RI i,t=0;for(i=1;i<=n;++i) g[i]=0;for(i=1;i<=m;++i)
p[i]>x&&(++g[a[i]],++g[b[i]],g[T.LCA(a[i],b[i])]-=2,++t);//差分
RI k=0;for(dfs(),i=2;i<=n;++i) g[i]==t&&k<v[i]&&(k=v[i]);return Mx-k<=x;//求出交集中的最大边权
}
int main()
{
RI i,x,y,z;for(F.read(n,m),i=1;i^n;++i) F.read(x,y,z),add(x,y,z),add(y,x,z);
for(T.Init(),i=1;i<=m;++i) F.read(a[i],b[i]),Mx<(p[i]=T.Dis(a[i],b[i]))&&(Mx=p[i]);//预处理出每条路径长度
RI l=0,r=1e9,mid;W(l<r) Check(mid=l+r-1>>1)?r=mid:l=mid+1;return printf("%d
",r),0;//二分答案
}