学习大佬:树的直径求法及证明

树的直径

定义:

一棵树的直径就是这棵树上存在的最长路径。

给定一棵树,树中每条边都有一个权值,树中两点之间的距离定义为连接两点的路径边权之和。树中最远的两个节点之间的距离被称为树的直径,连接这两点的路径被称为树的最长链。后者通常也可称为直径,即直径是一个数值概念,也可代指一条路径。

求法:

一、树形dp

时间复杂度:O( n );

优点:代码量少实现方便。

不足:不容易记录路径。

实现过程:

状态:d[ x ] 以当前结点 x 为根的 子树的直径。

我们枚举每一个结点 x 以及 它要到达的下一个结点 Eiv。

这两个结点所能达到的最大距离之和 加上 这两个结点的边权就有可能去更新树的直径。

即:树的直径 ans_max = max{ d[ x ] + d[ Eiv ] + edge[x, Eiv] } (1 <= x <= N)

那么 d[ x ] 通过什么更新呢?当然是由 它所连接下一个结点所能达到最大距离 来更新了;

即 d[ x ] = max{ d[ x ], d[ Eiv ] + edge[ x, Eiv ] };

核心代码:

 void dp(int st)
{
vis[st] = true; //当前结点已访问
for(int i = head[st]; i != -; i = edge[i].nxt){
int Eiv = edge[i].v;
if(vis[Eiv]) continue; //不走回头路
dp(Eiv);
ans_max = max(ans_max, d[st] + d[Eiv] + edge[i].w); //更新树的直径(由当前结点两段之和更新)
d[st] = max(d[st], d[Eiv]+edge[i].w); //更新当前结点所能走的最长路径(保留较长的那边)
}
}

二、DFS || BFS 

时间复杂度: O( n );

优点:可以在第一次 dfs/bfs记录前驱

不足:代码量稍大

实现过程:

前提:

两次dfs或bfs。第一次任意选一个点进行dfs(bfs)找到离它最远的点,此点就是最长路的一个端点,再以此点进行dfs(bfs),找到离它最远的点,此点就是最长路的另一个端点,于是就找到了树的直径。

证明(by大佬):

假设此树的最长路径是从s到t,我们选择的点为u。反证法:假设搜到的点是v。
1、v在这条最长路径上,那么dis[u,v]>dis[u,v]+dis[v,s],显然矛盾。
2、v不在这条最长路径上,我们在最长路径上选择一个点为po,则dis[u,v]>dis[u,po]+dis[po,t],那么有dis[s,v]=dis[s,po]+dis[po,u]+dis[u,v]>dis[s,po]+dis[po,t]=dis[s,t],即dis[s,v]>dis[s,t],矛盾。
也许你想说u本身就在最长路径,或则其它的一些情况,但其实都能用类似于上面的反证法来证明的。
综上所述,你两次dfs(bfs)就可以求出最长路径的两个端点和路径长度。

核心代码:

 void dfs(int s)
{
for(int i = head[s]; i != -; i = edge[i].nxt){
int Eiv = edge[i].v;
if(fa[s] == Eiv) continue; //不走回头路,也可以递归父亲结点省去fa数组空间
fa[Eiv] = s;
dis[Eiv] = dis[s] + edge[i].w; //当前结点最长路径
dfs(Eiv);
}
}

三、举栗子

1、POJ 1985 Cow Marathon

Cow Marathon

Time Limit: 2000MS   Memory Limit: 30000K
Total Submissions: 6925   Accepted: 3279
Case Time Limit: 1000MS

Description

After hearing about the epidemic of obesity in the USA, Farmer John wants his cows to get more exercise, so he has committed to create a bovine marathon for his cows to run. The marathon route will include a pair of farms and a path comprised of a sequence of roads between them. Since FJ wants the cows to get as much exercise as possible he wants to find the two farms on his map that are the farthest apart from each other (distance being measured in terms of total length of road on the path between the two farms). Help him determine the distances between this farthest pair of farms. 

Input

* Lines 1.....: Same input format as "Navigation Nightmare".

Output

* Line 1: An integer giving the distance between the farthest pair of farms. 

Sample Input

7 6
1 6 13 E
6 3 9 E
3 5 7 S
4 1 3 N
2 4 20 W
4 7 2 S

Sample Output

52

Hint

The longest marathon runs from farm 2 via roads 4, 1, 6 and 3 to farm 5 and is of length 20+3+13+9+7=52. 

题意概括:

建一颗 N 个节点 M 条边的树,求树上两点最长距离(树的直径);方向都是虚的,建双向边。

解题思路:

①树形dp

AC code:

 //树形dp版
#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <vector>
#define INF 0x3f3f3f3f
using namespace std;
const int MAXN = 5e4+;
int d[MAXN];
bool vis[MAXN];
int head[MAXN], cnt;
int N, M, ans_max;
struct Edge
{
int v, w, nxt;
Edge(int _v = , int _w = , int _nxt = ):v(_v), w(_w), nxt(_nxt){};
}edge[MAXN<<]; void init()
{
memset(head, -, sizeof(head));
memset(d, , sizeof(d));
memset(vis, false, sizeof(vis));
cnt = ;
} void AddEdge(int from, int to, int weight)
{
edge[cnt] = Edge(to, weight, head[from]);
head[from] = cnt++;
} void dp(int st)
{
vis[st] = true; //当前结点已访问
for(int i = head[st]; i != -; i = edge[i].nxt){
int Eiv = edge[i].v;
if(vis[Eiv]) continue; //不走回头路
dp(Eiv);
ans_max = max(ans_max, d[st] + d[Eiv] + edge[i].w); //更新树的直径(由当前结点两段之和更新)
d[st] = max(d[st], d[Eiv]+edge[i].w); //更新当前结点所能走的最长路径(保留较长的那边)
}
} int main()
{
while(~scanf("%d%d", &N, &M))
{
init();
char ccc;
for(int i = , u, v, w; i <= M; i++){
scanf("%d%d%d %c", &u, &v, &w, &ccc);
AddEdge(u, v, w);
AddEdge(v, u, w);
}
//printf("%d\n", ans);
ans_max = ;
dp();
printf("%d\n", ans_max);
}
return ;
}

②两次DFS

AC code:

 //DFS版
/*
#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <vector>
#define INF 0x3f3f3f3f
using namespace std;
const int MAXN = 5e4+5;
int dis[MAXN], fa[MAXN];
int head[MAXN], cnt;
int N, M, ans; struct Edge
{
int v, w, nxt;
Edge(int _v = 0, int _w = 0, int _nxt = 0):v(_v), w(_w), nxt(_nxt){};
}edge[MAXN<<1]; void AddEdge(int from, int to, int weight)
{
edge[cnt] = Edge(to, weight, head[from]);
head[from] = cnt++;
} void init()
{
memset(head, -1, sizeof(head));
for(int i = 0; i <= N; i++) fa[i] = i;
cnt = 0;
ans = 0;
} void dfs(int s)
{
for(int i = head[s]; i != -1; i = edge[i].nxt){
int Eiv = edge[i].v;
if(fa[s] == Eiv) continue; //不走回头路,也可以递归父亲结点省去fa数组空间
fa[Eiv] = s;
dis[Eiv] = dis[s] + edge[i].w; //当前结点最长路径
dfs(Eiv);
}
} int main()
{
while(~scanf("%d%d", &N, &M))
{
init();
char ccc;
for(int i = 1, u, v, w; i <= M; i++){
scanf("%d%d%d %c", &u, &v, &w, &ccc);
AddEdge(u, v, w);
AddEdge(v, u, w);
}
//printf("%d\n", ans);
int ans_max = 0, ans_index = 0;
dfs(1); //第一次dfs找出树的直径所在的点
for(int i = 1; i <= N; i++){
if(dis[i] > ans_max){
ans_max = dis[i];
ans_index = i;
}
dis[i] = 0;
fa[i] = i;
} dfs(ans_index); //第二次dfs找出树的直径
for(int i = 1; i <= N; i++){
if(dis[i] > ans_max) ans_max = dis[i];
}
printf("%d\n", ans_max);
}
return 0;
}

2、POJ 3310 Caterpillar

Caterpillar

Time Limit: 2000MS   Memory Limit: 65536K
Total Submissions: 2505   Accepted: 1180

Description

An undirected graph is called a caterpillar if it is connected, has no cycles, and there is a path in the graph where every node is either on this path or a neighbor of a node on the path. This path is called the spine of the caterpillar and the spine may not be unique. You are simply going to check graphs to see if they are caterpillars.

For example, the left graph below is not a caterpillar, but the right graph is. One possible spine is
shown by dots.

Input

There will be multiple test cases. Each test case starts with a line containing n indicating the number of nodes, numbered 1 through n (a value of n = 0 indicates end-of-input). The next line will contain an integer e indicating the number of edges. Starting on the following line will be e pairs n1 n2 indicating an undirected edge between nodes n1 and n1. This information may span multiple lines. You may assume that n ≤ 100 and e ≤ 300. Do not assume that the graphs in the test cases are connected or acyclic.

Output

For each test case generate one line of output. This line should either be

Graph g is a caterpillar.
or
    Graph g is not a caterpillar.

as appropriate, where g is the number of the graph, starting at 1.

Sample Input

22
21
1 2 2 3 2 4 2 5 2 6 6 7 6 10 10 8 9 10 10 12 11 12 12 13 12 17
18 17 15 17 15 14 16 15 17 20 20 21 20 22 20 19
16
15
1 2 2 3 5 2 4 2 2 6 6 7 6 8 6 9 9 10 10 12 10 11 10 14 10 13 13 16 13 15
0

Sample Output

Graph 1 is not a caterpillar.
Graph 2 is a caterpillar.

题意概括:

首先需要判断给的图是不是一棵树;

其次需要判断是否存在一条路径使得图上所有的点要么在这条路径上,要么距离该路径的距离为 1。

解题思路:

判断是否为一棵树直接dfs判断是否存在环即可;

为了让最多的点满足第二个条件,则这条路径一定是树的直径。

两次DFS求出树的直径,遍历一遍判断是否所有点都满足条件。

为了做这个判断,DFS时不仅要更新树的直径还要同时更新每一个结点能达到的最长路径;

如果当前结点能达到的最长路径等于树的直径说明该结点在树的直径上,如果不能则判断他是否能通过相连的结点到达树的直径。

AC code:

 #include <bits/stdc++.h>
#define INF 0x3f3f3f3f
using namespace std;
const int MAXN = ;
struct Edge{int v; int nxt;}edge[MAXN<<];
int head[MAXN], cnt;
int dis[MAXN], bb[MAXN];
bool vis[MAXN];
int N, M;
int ans_max, ans_index;
bool chck; void init()
{
memset(head, -, sizeof(head));
memset(vis, false, sizeof(vis));
memset(dis, , sizeof(dis));
memset(bb, , sizeof(bb));
cnt = ;
chck = false;
ans_max = ; ans_index = ;
} void AddEdge(int from, int to)
{
edge[cnt].v = to;
edge[cnt]. nxt = head[from];
head[from] = cnt++;
} void check(int s, int fa)
{
vis[s] = true;
for(int i = head[s]; i != -; i = edge[i].nxt){
int Eiv = edge[i].v;
if(Eiv == fa) continue;
if(vis[Eiv]) {chck = true; return;}
check(Eiv, s);
if(chck) return;
}
} void dfs(int s, int fa)
{
for(int i = head[s]; i != -; i = edge[i].nxt){
int Eiv = edge[i].v;
if(Eiv != fa){
dis[Eiv] = dis[s] + ;
bb[Eiv] = dis[Eiv];
if(dis[Eiv] > ans_max) {ans_max = dis[Eiv]; ans_index = Eiv;} //更新树的直径
dfs(Eiv, s);
bb[s] = max(bb[s], bb[Eiv]); //更新当前结点最长路径
}
}
} int main()
{
int T_case = ;
while(~scanf("%d", &N) && N){
scanf("%d", &M);
init();
for(int m = , u, v; m <= M; m++){
scanf("%d%d", &u, &v);
AddEdge(u, v);
AddEdge(v, u);
} if(M > N-) chck = true;
if(!chck) check(, ); //判断是否存在环
if(!chck){
for(int i = ; i <= N; i++){
if(!vis[i]){chck = true; break;} //判断所有点是否联通
}
if(!chck){
dis[] = ;
dfs(, ); //第一次dfs找出树的直径所在的点
dis[ans_index] = ;
dfs(ans_index, ); //第二次dfs找出树的直径
for(int i = ; i <= N; i++){ //判断所有的点是否满足条件
bool flag = false;
if(bb[i] == ans_max) continue; //在直径上
for(int k = head[i]; k != -; k = edge[k].nxt){ //判断不在树的直径上的点到树的直径距离是否为 1
if(bb[edge[k].v] == ans_max) {flag = true; break;}
}
if(!flag) {chck = true; break;}
}
}
}
if(chck) printf("Graph %d is not a caterpillar.\n", ++T_case);
else printf("Graph %d is a caterpillar.\n", ++T_case);
}
return ;
}

树的直径的求法即相关证明【树形DP || DFS】的更多相关文章

  1. 树的直径新求法、codeforces 690C3 Brain Network (hard)

    树的直径新求法 讲解题目 今天考了一道题目,下面的思路二是我在考场上原创,好像没人想到这种做法,最原始的题目,考场上的题目是这样的: 你现在有1 个节点,他的标号为1,每次加入一个节点,第i 次加入的 ...

  2. HDU 5293 Annoying problem 树形dp dfs序 树状数组 lca

    Annoying problem 题目连接: http://acm.hdu.edu.cn/showproblem.php?pid=5293 Description Coco has a tree, w ...

  3. 树形DP+DFS序+树状数组 HDOJ 5293 Tree chain problem(树链问题)

    题目链接 题意: 有n个点的一棵树.其中树上有m条已知的链,每条链有一个权值.从中选出任意个不相交的链使得链的权值和最大. 思路: 树形DP.设dp[i]表示i的子树下的最优权值和,sum[i]表示不 ...

  4. HDU 5293 Tree chain problem 树形dp+dfs序+树状数组+LCA

    题目链接: http://acm.hdu.edu.cn/showproblem.php?pid=5293 题意: 给你一些链,每条链都有自己的价值,求不相交不重合的链能够组成的最大价值. 题解: 树形 ...

  5. 刷题总结——Tree chain problem(HDU 5293 树形dp+dfs序+树状数组)

    题目: Problem Description Coco has a tree, whose vertices are conveniently labeled by 1,2,…,n.There ar ...

  6. SPOJ 1479 +SPOJ 666 无向树最小点覆盖 ,第二题要方案数,树形dp

    题意:求一颗无向树的最小点覆盖. 本来一看是最小点覆盖,直接一下敲了二分图求最小割,TLE. 树形DP,叫的这么玄乎,本来是线性DP是线上往前\后推,而树形DP就是在树上,由叶子结点状态向根状态推. ...

  7. BZOJ4784 ZJOI2017仙人掌(树形dp+dfs树)

    首先考虑是棵树的话怎么做.可以发现相当于在树上选择一些长度>=2的路径使其没有交,同时也就相当于用一些没有交的路径覆盖整棵树. 那么设f[i]为覆盖i子树的方案数.转移时考虑包含根的路径.注意到 ...

  8. Codeforces 592D - Super M - [树的直径][DFS]

    Time limit 2000 ms Memory limit 262144 kB Source Codeforces Round #328 (Div. 2) Ari the monster is n ...

  9. poj:1985:Cow Marathon(求树的直径)

    Cow Marathon Time Limit: 2000MS   Memory Limit: 30000K Total Submissions: 5496   Accepted: 2685 Case ...

随机推荐

  1. 接口隔离原则(Interface Segregation Principle)ISP

    using System; using System.Collections.Generic; using System.Text; namespace InterfaceSegregationPri ...

  2. Docker的基本构架

    不多说,直接上干货! Docker的基本构架 Docker基于Client-Server架构,Docker daemon是服务端,Docker client是客户端. Docker的基本架构,如下图所 ...

  3. 使用bind配置DNS服务(CentOS 6.5)

    DNS域名解析服务(Domain Name System)是用于解析域名与IP地址对应关系的服务,功能上可以实现正向解析与反向解析: 正向解析:根据主机名(域名)查找对应的IP地址. 反向解析:根据I ...

  4. DotNetCore跨平台~xUnit和测试报告

    在进入dotnet core时代之后,测试驱动开发TDD的主要工具不再是微软的nunit,取而代之的是更通用的xunit,微软把它集成到了dotnetcore的项目里,在安装完成vs2017之后,你可 ...

  5. SpringBoot 之 打war包

    1.修改打包方式为 war <packaging>war</packaging> 2. 修改tomcat 依赖 <dependency> <groupId&g ...

  6. uwsgi服务启动、关闭、重启操作

    1.      添加uwsgi相关文件 在之前的文章跟讲到过centos中搭建nginx+uwsgi+flask运行环境,本节就基于那一次的配置进行说明. 在www中创建uwsgi文件夹,用来存放uw ...

  7. 阻止事件的默认行为,例如click <a>后的跳转~

    在W3C中,使用preventDefault()方法: 在IE中,使用window.event.returnValue = false.

  8. nyoj 600——花儿朵朵——【离散化、线段树插线问点】

    花儿朵朵 时间限制:1000 ms  |  内存限制:65535 KB 难度:5   描述 春天到了,花儿朵朵盛开,hrdv是一座大花园的主人,在他的花园里种着许多种鲜花,每当这个时候,就会有一大群游 ...

  9. 【ubuntu】给新装好的UBUNTU系统配置静态IP

    最近在自己装有win7系统的thinkpad电脑上,给安装了Ubuntu16.04双系统. 想在ubuntu下配置一个hadoop伪分布式,最首要的就是要给系统配置一个静态IP . 一开始我按照网上的 ...

  10. 网站部署中遇到的问题-网页中js,css和图片资源无法加载

    问题描述: 打开的网页跑版,图片无法加载,用控制台调试发现css和js都没有加载. 原因: 没有启用IIS"静态内容". 解决方法: 设置"打开或关闭windows功能& ...