求最小偏心距

洛谷P1099
参考的是这篇题解:
完整代码:https://www.luogu.com.cn/blog/123hei/solution-p1099
基于以上代码的详细讲解:https://www.luogu.com.cn/blog/leathermanfuckyou/solution-p1099
本文也是基于第一篇博客代码在讲解上的一些补充,设计的知识点有

  • 链式前向星
  • dfs求树的直径
  • 树上尺取

0.链式前向星

  • 知乎的解释吧,我就不花时间复述了,我这里补充几点便于记忆
  • head[u]存的是以u为出发点的第一条弧的编号
  • edge.nxt存的也是弧的编号,存的是下一条弧的编号

1.求树的直径

两次深度遍历,第一次是找到一个端点,第二次是找到另一个端点

void dfs(int f,int x){
    fa[x]=f;
    if(dis[x]>dis[k])k=x;
    for(int i=head[x];i;i=E[i].nxt){
        int y=E[i].to;
        if(y==f||mark[y])continue;
        dis[y]=dis[x]+E[i].w;
        dfs(x,y);
    }
}

主函数中的调用如下

dfs(0,1);
dis[k]=0,dfs(0,k);
 //k表示最远的端点
 top=k;//确定直径

2.dis[]数组

  • 这个数组的意义随深度遍历的起点不同而不同,但都是树上的点到遍历起点的距离
  • 我们记第二次深度遍历前的k点为端点A,dis数组在经过第二次深度遍历,也就是dfs(0,k)后,它的意义是树上的一些点到端点A的距离
  • 在继续本篇文章继续探索部分,dis数组的意义就是这个树中的点到直径的距离

3.树上尺取

沿用第2个点中的说法,记第二次深度遍历的遍历起点是端点A,即直径的一个端点是A;我们来另外约定一下,这条直径的另一个端点是B,也就是下面for循环中的top

//i是头,j是尾巴
    for(int i=top,j=top,l=1,r=0;i;i=fa[i]){
        //尺取,这个while就是在选取路径
        while(dis[j]-dis[i]>m)
            j=fa[j];
        //dis[top]-dis[j]是尾部到端点B的距离,dis[i]是头部到端点A的距离
        //x求的就是当前所选路径的偏心距
        x=max(dis[top]-dis[j],dis[i]);
        //ans相当于一个全局变量,求各次选取的路径的偏心距的最小值
        ans=min(ans,x);
    }

4.继续探索(选取的路径就是直径怎么办)

完成求直径和树上尺取后,就完成了大部分工作,但是考虑一下以下输入数据:

5 12
1 2 4
2 3 4
3 4 4
2 5 3

这样选取的路径就是一整条直径,这样就不能在直径上选取最小偏心距了,处理方法如下:

for(int i=top;i;i=fa[i])mark[i]=1;
//标记直径,重新计算非直径上的点到直径的距离
for(int i=top;i;i=fa[i]){
	k=i,dis[k]=0;
	dfs(fa[i],i);
}
//这个for的作用就是比较直径上的点更远还是非直径上的点更远
//非直径上的点更远,只有在选取的路径就是这条直径时才会发生
for(int i=1;i<=n;i++)
	ans=max(ans,dis[i]);

5.我的完整代码

这个代码是我参考第一篇博客写的,思路大概一致,删掉了一些冗余的代码

#include <cmath>
#include <cstdio>
#include <stdlib.h>
#include <algorithm>

#define maxn 600

using namespace std;

struct edge{
    int to;
    int w;
    int nxt;
}E[maxn*2];

int id,head[maxn];

void add(int u,int v,int w){
    E[++id]=((edge){v,w,head[u]});
    head[u]=id;
}

int k,father[maxn],dis[maxn];
int mark[maxn];

void dfs(int f,int x){
    father[x]=f;
    if(dis[x]>dis[k])
        k=x;
    for(int e=head[x];e!=0;e=E[e].nxt){
        int y=E[e].to;
        if(y==f||mark[y])
            continue;
        dis[y]=dis[x]+E[e].w;
        dfs(x,y);
    }
}

int n,s;
int u,v,w;
int top;
int ecc;
int min_ecc=100000010;
int main(){
    scanf("%d%d",&n,&s);
    for(int i=1;i<=n-1;i++){
        scanf("%d%d%d",&u,&v,&w);
        add(u,v,w);
        add(v,u,w);
    }

    //找直径
    dfs(0,1);
    dis[k]=0;
    dfs(0,k);
    top=k;

    //树上尺取
    for(int i=top,j=top;i!=0;i=father[i]){
        while(dis[j]-dis[i]>s)
            j=father[j];
        ecc=max(dis[top]-dis[j],dis[i]);
        min_ecc=min(ecc,min_ecc);
    }

    //标记直径
    for(int i=top;i!=0;i=father[i])
        mark[i]=1;

//    找非直径上的点到直径的距离
    for(int i=top;i!=0;i=father[i]){
        dis[i]=0;
        dfs(father[i],i);
    }

    for(int i=1;i<=n;i++)
        min_ecc=max(min_ecc,dis[i]);

    printf("%d",min_ecc);
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值