快递员 点分治

快递员

LG传送门

点分治好题。然而似乎不是一般意义上的点分治?

首先要看清楚题,我就被题意卡了,这道题是在一棵树上给出\(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最后一个点。

转载于:https://www.cnblogs.com/cj-chd/p/10109606.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值