【刷题】树的宽度优先遍历 / 树型DP——Cow Marathon【树的直径】

在这里插入图片描述
题目链接

求树中最长的一条路径,也就是在树中找到两个点,使其距离最远。

样例输入n个点,m条边。接下去m行,每行代表点a和b之间有一条边,边权是c,最后的字母代表边的方向(在这题没用)。

数据范围n <= 100000


1、遍历两次树,bfs或dfs均可(只能处理边权为正的)

参考链接
方法:先从任意一点P出发,找离它最远的点Q,再从点Q出发,找离它最远的点W,W到Q的距离就是是的直径

证明如下:

①若P已经在直径上,根据树的直径的定义可知Q也在直径上且为直径的一个端点

②若P不在直径上,我们用反证法,假设此时WQ不是直径,AB是直径

—>若AB与PQ有交点C,由于P到Q最远,那么PC+CQ>PC+CA,所以CQ>CA,易得CQ+CB>CA+CB,即CQ+CB>AB,与AB是直径矛盾,不成立,如下图(其中AB,PQ不一定是直线,画成直线是为了方便):
在这里插入图片描述

—>若AB与PQ没有交点,M为AB上任意一点,N为PQ上任意一点。P到Q最远,NP+NQ>NQ+MN+MB,同时减掉NQ,得NP>MN+MB,所以有NP+MN > NP-MN > MB,所以NP+MN+MA > MB+MA,即NP+MN+MA > AB,与AB是直径矛盾,所以这种情况也不成立,如下图:
在这里插入图片描述

这边采用bfs进行遍历,遍历模板

queue<int> q;
st[1] = true; // 表示1号点已经被遍历过
q.push(1);

while (q.size())
{
    int t = q.front();
    q.pop();

    for (int i = head[t]; i != -1; i = nxt[i])
    {
        int j = val[i];
        if (!st[j])
        {
            st[j] = true; // 表示点j已经被遍历过
            q.push(j);
        }
    }
}

该题代码

#include <iostream>
#include <cstring>
#include <queue>
using namespace std;

const int N = 100005;

int n, m;
int lnk[N], val[N], nxt[N], head[N], idx;
queue<pair<int, int>> q;
bool visit[N];

void add(int a, int b, int w) {
	lnk[idx] = b;
	val[idx] = w;
	nxt[idx] = head[a];
	head[a] = idx ++ ;
}

pair<int, int> bfs(int root) {
	memset(visit, 0, sizeof visit);
	int res = 0, len = 0;
	
	q.push(make_pair(root, 0));
	visit[root] = true; 
	while(!q.empty()) {
		pair<int, int> p = q.front();
		q.pop();
		for (int i = head[p.first]; i != -1; i = nxt[i]) {
			pair<int, int> node = make_pair(lnk[i], p.second + val[i]);
			if (!visit[node.first]) {
				q.push(node);
				visit[node.first] = true;
				
				if (len < node.second) {
					len = node.second;
					res = node.first;
				}
			}
		}
	}
	return make_pair(res, len);
}

int main() {
	scanf("%d%d", &n, &m);
	int a, b, c;
	char ch;
	memset(head, -1, sizeof head);
	idx = 0;
	
	for (int i = 1; i <= m; i ++ ) {
		scanf("%d%d%d %c", &a, &b, &c, &ch);
		add(a, b, c);
		add(b, a, c);
	}
	pair<int, int> x = bfs(1);
	pair<int, int> y = bfs(x.first);
	
	printf("%d\n", y.second);
	return 0;
} 

2、树形DP做法(边权为负也可处理)

如果子问题表示为:以这个点为根的子树的直径最大值。
因为最长的直径和这个点没有直接关联,因此难以从子问题递推。

将子问题表示为:以这个点i为根,往下走的路径的最大值,记作f[i]
将每个点的f[i]算出来,这样就可以求得经过每个点的直径最大值。例如下图所示,蓝色线段是所有经过点i的路径中最大的。
那么f[i]等于它所有子节点最大的两个f之和,即f[i] = f[a] + f[c] + w[i, a] + w[i, c]

在这里插入图片描述采用dfs进行遍历,则不需要存储f。

#include <iostream>
#include <cstring>
using namespace std;

const int N = 100005;

int n, m, ans;
int lnk[2 * N], val[2 * N], nxt[2 * N], head[2 * N], idx;
bool visit[N];
void add(int a, int b, int w) {
	lnk[idx] = b;
	val[idx] = w;
	nxt[idx] = head[a];
	head[a] = idx ++ ;
}

int dfs(int node) {
	visit[node] = true;
	int dis = 0, d1 = 0, d2 = 0;
	for (int i = head[node]; i != -1; i = nxt[i]) {
		int son = lnk[i];
		if (!visit[son]) {
			dis = dfs(son) + val[i];
			if (d1 <= dis) d2 = d1, d1 = dis;
			else if (d2 < dis) d2 = dis;
		}
	}
	ans = max(ans, d1 + d2);
	return d1; 
}

int main() {
	scanf("%d%d", &n, &m);
	int a, b, c;
	char ch;
	memset(head, -1, sizeof head);
	idx = 0;
	
	for (int i = 1; i <= m; i ++ ) {
		scanf("%d%d%d %c", &a, &b, &c, &ch);
		add(a, b, c);
		add(b, a, c);
	}
	
	dfs(1);
	printf("%d\n", ans);
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值