快递员
点分治好题。然而似乎不是一般意义上的点分治?
首先要看清楚题,我就被题意卡了,这道题是在一棵树上给出\(m\)个点对,求树上一个点,使得对于其中任意一个点对,这个点到那两个点的距离和的最大值最小。乍一看还以为要二分,发现二分做不了。
先想\(nm\)暴力,需要发现一个性质。考虑在树上取点,取什么点的时候可以确定答案不会更优?考虑当前取到的点\(r\)出发dfs求出树上所有点到它的距离,找出能使距离和最大的\(q\)个点对:假如在这些点对中存在一个点对,满足点对中的两个点分别在\(r\)的不同子树中,那么我们就找到了答案,因为无论\(r\)取在任何地方,它到这个点对的两个点的距离和一定不会更短(可以手玩);假如存在两个点对,满足他们分别在\(r\)的不同子树中,那么我们也找到了答案,因为无论\(r\)怎样移动,这两个点对中至少有一个到\(r\)的距离和会增加。
这样我们就得到了复杂度\(O(nm)\)的算法:先在树上任取一个点,然后递归执行以下过程,从这个点出发求到树上每个点的距离,在\(m\)取出个点对中取出能使距离和最大的点对,对他们执行在上面提到的两种判断,如果满足就输出,不满足就把所选取的当前点往那些点对所在的子树中挪动(那些点对只可能在同一棵子树内),如果最后无处可挪也说明找到了答案。
但是这样的算法仍然不够优秀,我们有\(O(mlogn)\)的算法:把每次挪动改为跳到目标子树的重心,就降下了复杂度。事实上,这样做与一般的点分治有些不同只是,借用了点分治的思想,反倒有点类似于在序列上二分(可以这样理解,当树变成一条链的时候也的确是这样)。
#include<cstdio>
#include<cctype>
#include<cstdlib>
#define R register
#define I inline
using namespace std;
const int S=100003,N=200003,inf=0x7fffffff;
char buf[1000000],*p1,*p2;
I char gc(){return p1==p2&&(p2=(p1=buf)+fread(buf,1,S,stdin),p1==p2)?EOF:*p1++;}
I int rd(){
R int f=0; R char c=gc();
while(c<48||c>57) c=gc();
while(c>47&&c<58) f=f*10+(c^48),c=gc();
return f;
}
int h[S],s[N],g[N],w[N],t[S],v[S],a[S],b[S],k[S],p[S],q[S],c,n,m,u,r,o;
I int min(int x,int y){return x<y?x:y;}
I int max(int x,int y){return x>y?x:y;}
I void add(int x,int y,int z){s[++c]=h[x],h[x]=c,g[c]=y,w[c]=z;}
void gts(int x,int f){t[x]=1;
for(R int i=h[x],y;i;i=s[i]) if(!v[y=g[i]]&&y^f) gts(y,x),t[x]+=t[y];
}
void gtr(int x,int f,int a){R int i,y,m=0;
for(i=h[x];i;i=s[i]) if(!v[y=g[i]]&&y^f) gtr(y,x,a),m=max(m,t[y]);
m=max(m,a-t[x]); if(m<u) u=m,r=x;
}
void dfs(int x,int f,int d,int u){p[x]=u,k[x]=d;
for(R int i=h[x],y;i;i=s[i]) if((y=g[i])^f) dfs(y,x,d+w[i],u);
}
void dac(int x){
u=n,gts(x,0),gtr(x,0,t[x]);
if(v[r]) printf("%d",o),exit(0);
v[r]=1,k[r]=0;
R int i,u=0;
for(i=h[r];i;i=s[i]) dfs(g[i],r,w[i],g[i]);
for(i=1;i<=m;++i)
if(k[a[i]]+k[b[i]]>u) u=k[a[i]]+k[b[i]],q[q[0]=1]=i;
else if(k[a[i]]+k[b[i]]==u) q[++q[0]]=i;
for(o=min(o,u),u=0,i=1;i<=q[0];++i){
if(p[a[q[i]]]^p[b[q[i]]]) printf("%d",o),exit(0);
else if(!u) u=p[a[q[i]]];
else if(u^p[a[q[i]]]) printf("%d",o),exit(0);
}
dac(u);
}
int main(){
R int i,x,y,z;
for(n=rd(),m=rd(),i=1;i<n;++i) x=rd(),y=rd(),z=rd(),add(x,y,z),add(y,x,z);
for(i=1;i<=m;++i) a[i]=rd(),b[i]=rd();
o=inf,dac(1);
return 0;
}
注意求距离的时候把到自己的距离更新为\(0\),否则在洛谷上会WA最后一个点。