0x63.图论 - 树的直径与最近公共祖先

声明:
本系列博客是《算法竞赛进阶指南》+《算法竞赛入门经典》+《挑战程序设计竞赛》的学习笔记,主要是因为我三本都买了 按照《算法竞赛进阶指南》的目录顺序学习,包含书中的少部分重要知识点、例题解题报告及我个人的学习心得和对该算法的补充拓展,仅用于学习交流和复习,无任何商业用途。博客中部分内容来源于书本和网络(我尽量减少书中引用),由我个人整理总结(习题和代码可全都是我自己敲哒)部分内容由我个人编写而成,如果想要有更好的学习体验或者希望学习到更全面的知识,请于京东搜索购买正版图书:《算法竞赛进阶指南》——作者李煜东,强烈安利,好书不火系列,谢谢配合。


下方链接为学习笔记目录链接(中转站)

学习笔记目录链接


ACM-ICPC在线模板


一、树的直径(Diameter)

树上两点的距离定义为,从树上一点到另一点所经过的权值

当树上两点距离最大时,就称作树的直径,树的直径既可以指这个权值,也可以指这个路径 (路径也叫树的最长链)。

树的直径有两种方法,都是 O ( n ) O(n) O(n)的时间复杂度。

1.树形DP求树的直径

设以1号结点为根,那么n个结点n-1条边的无向图就可以看作一个有根树。
D [ x ] D[x] D[x]表示从结点x 出发走向以x为根的子树,能够到达的最远结点的距离。
设x的子结点为 y 1 , y 2 . . . y t , e d g e [ x , y ] y_1,y_2...y_t,edge[x,y] y1,y2...ytedge[x,y]表示边权
那么显然有:
D [ x ] = 1 ≤ i ≤ t m a x ( D [ y i ] + e d g e [ x , y i ] ) D[x] = ^{max}_{1≤i≤t}(D[y_i] + edge[x,y_i]) D[x]=1itmax(D[yi]+edge[x,yi])

F [ x ] F[x] F[x]为经过结点x的最长链的长度。
然后就是代码:

int ans;
int vis[N];
void dp(int u){
   
    vis[u] = 1;
    for(int i = head[u];i;i = nex[i]){
   
        int v = ver[i];
        if(vis[v])continue;
        dp(v);
        ans = max(ans,d[u] + d[v] + edge[i]);//看这条链是不是最大的
        d[x] = max(d[u],d[v] + edge[i]);//更新当前链长
    }
}

2.两次BFS/DFS求树的直径

我们可以先从任意一点开始DFS,记录下当前点所能到达的最远距离,这个点为P。

在从P开始DFS记录下所能达到的最远点的距离,这个点为Q。

P , Q P,Q P,Q就是直径的端点, d i s ( P , Q ) dis(P,Q) dis(P,Q)就是直径。
具体代码见下题

1.POJ 1985.Cow Marathon(DFS求树的直径模板题)

题意:有N个农田以及M条路,给出M条路的长度以及路的方向(这道题不影响,用不到),让你找到一条 两农田(任意的)间的路径,使得距离最长,并输出最长距离。
这里用dfs求直径,当然也可以用bfs和树形DP来做。

#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
#include<bitset>
#include<vector>
#include<queue>

#define over(i,s,t) for(register int i = s;i <= t;++i)
#define lver(i,t,s) for(register int i = t;i >= s;--i)
//#define int __int128
#define lowbit(p) p&(-p)
using namespace std;

typedef long long ll;
typedef pair<int,int> PII;
const int INF = 0x3f3f3f3f;
const int N = 5e5+7;
const int M = 2007;

int head[N],ver[N],tot,edge[N],nex[N];
int n,m,ans;
int dis[N],vis[N];

inline void add(int u,int v,int w){
   
    ver[++tot] = v;
    edge[tot] = w;
    nex[tot] = head[u];
    head[u] = tot;
}

//两次dfs一次求P一次求Q
void dfs(int u,int &ed){
   
    if(dis[u] > ans)ans = dis[u],ed = u;
    vis[u] = 1;
    for(int i = head[u];~i;i = nex[i]){
   
        int v = ver[i],w = edge[i];
        if(vis[v])continue;
        dis[v] = dis[u] + w;
        dfs(v,ed);
    }
    return ;
}


int p,q;
void solve(){
   
    dfs(1,p);
    ans = dis[p] = 0;
    memset(vis,0,sizeof vis);
    dfs(p,q);
    cout<<ans<<endl;
}
int main()
{
   
    while(scanf("%d%d",&n,&m) != EOF){
   
        memset(head,-1,sizeof head);
        memset(vis,0,sizeof vis);
        memset(dis,0,sizeof dis);
        tot = 0;
        over(i,1,m){
   
            int u,v,w;
            char ch[2];
            scanf("%d%d%d%s",&u,&v,&w,ch);
            add(u,v,w);
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

繁凡さん

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值