[蓝桥杯]真题讲解:景区导游(DFS遍历、图的存储、树上前缀和与LCA)

一、视频讲解

视频讲解
在这里插入图片描述

二、暴力代码

//暴力代码:DFS
#include<bits/stdc++.h>
#define endl '\n'
#define deb(x) cout << #x << " = " << x << '\n';
#define INF 0x3f3f3f3f
#define int long long
using namespace std;
const int N = 2e5 + 10;

typedef pair<int,int> pii;

map<pii, int>st;//记录从{x, y}的距离是多少
int a[N];


vector<pii>edge[N];//存图

//s表示你要求的路径的起点
//v表示你要求的路径的终点
//u表示你当前走到了哪个点
//father表示你当前这个点的父亲节点是谁。避免重复走造成死循环
//sum表示从s走到u的路径花费总和。
bool dfs(int s, int u, int father, int v, int sum)
{
	if(u == v)
	{
		st[{s, v}] = sum;
		st[{v, s}] = sum;
		// cout << s << " " << v << " " << sum << endl;
		return true;
	}

	for(int i = 0; i < edge[u].size(); i ++)
	{
		int son = edge[u][i].first;
		if(son == father)
			continue;
		int w = edge[u][i].second;
		if(dfs(s, son, u, v, sum + w))
			return true;
	}

	return false;
}

void solve()
{
	int n, k;
	cin >> n >> k;
	for(int i = 0; i < n - 1; i ++)
	{
		int x, y, t;
		cin >> x >> y >> t;
		edge[x].push_back({y, t});
		edge[y].push_back({x, t});
	}

	for(int i = 0; i < k; i ++)
		cin >> a[i];

	//求出完整路线的总花费

	//O(k * n)
	int ans = 0;
	for(int i = 0; i < k - 1; i ++)
	{
		dfs(a[i], a[i], -1, a[i + 1], 0);

		ans += st[{a[i] ,a[i + 1]}];
	}


	for(int i = 0; i < k; i ++)
	{
		int tmp = ans;
		if(i == 0)
			tmp -= st[{a[i], a[i + 1]}];
		else if(i == k - 1)
			tmp -= st[{a[i - 1], a[i]}];
		else
		{
			tmp -= st[{a[i - 1], a[i]}];
			tmp -= st[{a[i], a[i + 1]}];
			dfs(a[i - 1], a[i - 1], -1, a[i + 1], 0);
			tmp += st[{a[i - 1], a[i + 1]}];
		}
		cout << tmp << endl;
	}
	
}

signed main()
{
	ios::sync_with_stdio(0);
	cin.tie(0);
	cout.tie(0);
	int t = 1;
	//cin >> t;
	while(t--)
	solve();
}

三、正解代码

//景区导游:树上前缀和 + 最近公共祖先
#include<bits/stdc++.h>
#define int long long
using namespace std;
typedef pair<int, int> pii;
const int N = 1e5 + 10;
int a[N], siz[N], dep[N], fa[N], son[N], top[N];
int sum[N];
int n, k;
vector<pii>edge[N];

void dfs1(int u, int father)
{
	siz[u] = 1, dep[u] = dep[father] + 1;
	fa[u] = father;
	for(int i = 0; i < edge[u].size(); i ++)
	{
		int s = edge[u][i].first;
		if(s == father)
			continue;
		dfs1(s, u);
		siz[u] += siz[s];
		if(siz[son[u]] < siz[s])
			son[u] = s;
	}
}

void dfs2(int u, int t)
{
	top[u] = t;
	if(son[u] == 0)
		return;
	dfs2(son[u], t);
	for(int i = 0; i < edge[u].size(); i ++)
	{
		int s = edge[u][i].first;
		if(s == son[u] || s == fa[u])
			continue;
		dfs2(s, s);
	}
}

int lca(int u, int v)
{
	while(top[u] != top[v])
	{
		if(dep[top[u]] < dep[top[v]])
			swap(u, v);
		u = fa[top[u]];
	}
	return dep[u] < dep[v] ? u : v;
}

void cal_sum(int u)
{
	for(int i = 0; i < edge[u].size(); i ++)
	{
		int s = edge[u][i].first;
		if(s == fa[u])
			continue;
		int w = edge[u][i].second;
		sum[s] = sum[u] + w;
		cal_sum(s);
	}
}

void solve()
{
	cin >> n >> k;
	for(int i = 0; i < n - 1; i ++)
	{
		int x, y, t;
		cin >> x >> y >> t;
		edge[x].push_back({y, t});
		edge[y].push_back({x, t});
	}
	for(int i = 1; i <= k; i ++)
		cin >> a[i];

	//树链剖分
	dfs1(1, 0);
	dfs2(1, 1);

	//求树上的前缀和
	cal_sum(1);

	int ans = 0;
	for(int i = 1; i <= k - 1; i ++)
	{
		int u = a[i], v = a[i + 1];
		int cost = sum[u] + sum[v] - 2 * sum[lca(u, v)];
		ans += cost;
	}
	for(int i = 1; i <= k; i ++)
	{
		int tmp = ans;
		if(i == 1)
			tmp -= sum[a[i + 1]] + sum[a[i]] - sum[lca(a[i], a[i + 1])] * 2;
		else if(i == k)
			tmp -= sum[a[i - 1]] + sum[a[i]] - sum[lca(a[i], a[i - 1])] * 2;
		else
		{
			tmp -= sum[a[i + 1]] + sum[a[i]] - sum[lca(a[i], a[i + 1])] * 2;
			tmp -= sum[a[i - 1]] + sum[a[i]] - sum[lca(a[i], a[i - 1])] * 2;
			tmp += sum[a[i - 1]] + sum[a[i + 1]] - sum[lca(a[i + 1], a[i - 1])] * 2;
		}
		cout << tmp << " ";
	}
	cout << endl;
}

signed main()
{
	ios::sync_with_stdio(0);
	cin.tie(0);
	int t = 1;
	while(t--)
	solve();
}

  • 34
    点赞
  • 45
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
要实现这个功能,可以采用二叉搜索树的特性,即左子树中所有节点的值小于根节点的值,右子树中所有节点的值大于根节点的值。因此,在先序遍历序列中,根节点的值必定是序列的第一个元素。 具体的实现步骤如下: 1. 定义一个二叉树的结构体,包含键值和左右子节点指针。 2. 定义一个函数,输入先序遍历序列和序列长度,返回根节点。 3. 从先序遍历序列中取出第一个元素作为根节点。 4. 遍历序列,将小于根节点值的元素放入左子树序列,大于根节点值的元素放入右子树序列。 5. 递归创建左右子树,并将左右子树的根节点分别挂在根节点的左右子节点上。 6. 采用递归的方式遍历二叉树,查找节点U和V的位置。 7. 如果当前节点为NULL或者等于U或V,则返回当前节点。 8. 如果U和V分别在当前节点的左右子树中,则当前节点为最近公共祖先。 9. 如果U和V在当前节点的同一子树中,则继续向下递归。 10. 最终返回最近公共祖先节点的键值即可。 下面是实现代码的示例: ```c #include <stdio.h> #include <stdlib.h> typedef struct Node { int key; struct Node* left; struct Node* right; } Node; Node* create_bst(int* preorder, int len) { if (len == 0) { return NULL; } Node* root = (Node*) malloc(sizeof(Node)); root->key = preorder[0]; int i; for (i = 1; i < len; i++) { if (preorder[i] > root->key) { break; } } root->left = create_bst(preorder + 1, i - 1); root->right = create_bst(preorder + i, len - i); return root; } Node* find_lca(Node* root, int u, int v) { if (root == NULL || root->key == u || root->key == v) { return root; } if (u < root->key && v < root->key) { return find_lca(root->left, u, v); } else if (u > root->key && v > root->key) { return find_lca(root->right, u, v); } else { return root; } } int main() { int preorder[] = {6, 2, 1, 4, 3, 5, 9, 7, 10}; int len = sizeof(preorder) / sizeof(preorder[0]); Node* root = create_bst(preorder, len); int u = 3, v = 5; Node* lca = find_lca(root, u, v); printf("LCA of %d and %d is %d\n", u, v, lca->key); u = 4, v = 9; lca = find_lca(root, u, v); printf("LCA of %d and %d is %d\n", u, v, lca->key); u = 4, v = 5; lca = find_lca(root, u, v); printf("LCA of %d and %d is %d\n", u, v, lca->key); return 0; } ``` 输出结果如下: ``` LCA of 3 and 5 is 4 LCA of 4 and 9 is 6 LCA of 4 and 5 is 4 ```

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值