文章目录
- DFS的概念
- 蓝桥杯:大臣的旅费
- 问题描述
- 输入格式
- 输出格式
- 解题理解
- 解题源码
- 参考目录
DFS的概念
- 深度优先搜索(depth-first seach,DFS)在搜索到一个新的节点时,立即对该新节点进行遍历;因此遍历需要用先入后出的栈来实现,也可以通过与栈等价的递归来实现。
- 深度优先搜索也可以用来检测环路:记录每个遍历过的节点的父节点,若一个节点被再次遍历且父节点不同,则说明有环。我们也可以用之后会讲到的拓扑排序判断是否有环路,若最后存在入度不为零的点,则说明有环。
- 有时我们可能会需要对已经搜索过的节点进行标,
修改当前节点状态
->遍历下一个节点
,比回溯法
少了状态回改这一步。
蓝桥杯:大臣的旅费
问题描述
- 很久以前,T王国空前繁荣。为了更好地管理国家,王国修建了大量的快速路,用于连接首都和王国内的各大城市。
- 为节省经费,T国的大臣们经过思考,制定了一套优秀的修建方案,使得任何一个大城市都能从首都直接或者通过其他大城市间接到达。同时,如果不重复经过大城市,从首都到达每个大城市的方案都是唯一的。
- J是T国重要大臣,他巡查于各大城市之间,体察民情。所以,从一个城市马不停蹄地到另一个城市成了J最常做的事情。他有一个钱袋,用于存放往来城市间的路费。
- 聪明的J发现,如果不在某个城市停下来修整,在连续行进过程中,他所花的路费与他已走过的距离有关,在走第x千米到第x+1千米这一千米中(x是整数),他花费的路费是x+10这么多。也就是说走1千米花费11,走2千米要花费23。
- J大臣想知道:他从某一个城市出发,中间不休息,到达另一个城市,所有可能花费的路费中最多是多少呢?
输入格式
- 输入的第一行包含一个整数
n
,表示包括首都在内的T
王国的城市数 - 城市从
1
开始依次编号,1
号城市为首都。 - 接下来
n-1
行,描述T国的高速路(T国的高速路一定是n-1条) - 每行三个整数
Pi,Qi,Di
,表示城市Pi
和城市Qi
之间有一条高速路,长度为Di千米。
输出格式
- 输出一个整数,表示大臣J最多花费的路费是多少。
解题理解
- 从题目出发,这道题的想法也就是要在一个无向无环图中,找到一条最长径,即求出树的直径。
解题源码
- 第一个版本:直接使用
邻接矩阵
,dfs
遍历以每一个顶点开始的最长径,取最大的一条,在蓝桥杯的评测记录上,这种解法通过了前三个测试用例,第四个测试用例:1.超时,2.且栈内存超过上限。
#include <iostream>
#include <vector>
using namespace std;
void findroad(vector<vector<int> > & Graph, vector<int> & visted,\
int Graph_v_id, int maxroad);
int cost(int road);
int realmaxroad = 0;
int main() {
int v_nums;
// 保存图的顶点数
cin >> v_nums;
// 开始使用邻接矩阵进行建图
vector<vector<int> > Graph(v_nums + 1, vector<int>(v_nums + 1, 0));
// 每个顶点的遍历情况
vector<int> visted(v_nums + 1, 0);
int v1, v2, v1tov2;
// 记录v_nums - 1条路径情况
for (int i = 0; i < v_nums - 1; ++i) {
cin >> v1 >> v2 >> v1tov2;
Graph[v1][v2] = v1tov2;
Graph[v2][v1] = v1tov2;
}
// 开始找到最长的一条路
int maxroad = 0;
for (int i = 1; i < visted.size(); ++i) {
// 从首都开始查找
findroad(Graph, visted, i, maxroad);
// 重新置0
std::fill(visted.begin(), visted.end(), 0);
maxroad = 0;
}
// 费用计算
int maxcost = cost(realmaxroad);
cout << maxcost << endl;
return 0;
}
void findroad(vector<vector<int> > & Graph, vector<int> & visted,\
int Graph_v_id, int maxroad) {
// 修改当前节点状态
visted[Graph_v_id] = 1;
// 找当前节点的邻接点
for(int i = 1; i < visted.size(); ++i) {
// 如果这两个城市之间能够直达,同时没有被遍历过
if (Graph[Graph_v_id][i] && !visted[i]) {
// 将当前的路径长度记录下来
maxroad += Graph[Graph_v_id][i];
if(realmaxroad < maxroad) {
realmaxroad = maxroad;
}
findroad(Graph, visted, i, maxroad);
// 路径长度回溯,记录其他支路计算
maxroad -= Graph[Graph_v_id][i];
}
}
}
int cost (int road) {
return (road * 10 + (road + 1) * road / 2);
}
- 第二个版本:参考别人的解释,由树的直径的定义和关系解,使用两遍
dfs
即可求得:先从任意一点P出发,找离它最远的点Q,再从点Q出发,找离它最远的点W,W到Q的距离就是树的直径,在刚刚原来的代码上稍加修改就可,但是第四个测试用例也会出现内存超限的问题。
#include <iostream>
#include <vector>
using namespace std;
void findroad(vector<vector<int> > & Graph, vector<int> & visted,\
int Graph_v_id, int maxroad);
int cost(int road);
int realmaxroad = 0;
// 第一次dfs中最远的那个点
int second_v_id = 0;
int main() {
int v_nums;
// 保存图的顶点数
cin >> v_nums;
// 开始使用邻接矩阵进行建图
vector<vector<int> > Graph(v_nums + 1, vector<int>(v_nums + 1, 0));
// 每个顶点的遍历情况
vector<int> visted(v_nums + 1, 0);
int v1, v2, v1tov2;
// 记录v_nums - 1条路径情况
for (int i = 0; i < v_nums - 1; ++i) {
cin >> v1 >> v2 >> v1tov2;
Graph[v1][v2] = v1tov2;
Graph[v2][v1] = v1tov2;
}
// 开始找到最长的一条路
int maxroad = 0;
// 第一次dfs,从首都开始查找
findroad(Graph, visted, 1, maxroad);
// 重新置0
std::fill(visted.begin(), visted.end(), 0);
maxroad = 0;
// 第二次dfs
findroad(Graph, visted, second_v_id, maxroad);
cout << realmaxroad << endl;
// 费用计算
int maxcost = cost(realmaxroad);
cout << maxcost << endl;
return 0;
}
void findroad(vector<vector<int> > & Graph, vector<int> & visted,\
int Graph_v_id, int maxroad) {
// 修改当前节点状态
visted[Graph_v_id] = 1;
// 找当前节点的邻接点
for(int i = 1; i < visted.size(); ++i) {
// 如果这两个城市之间能够直达,同时没有被遍历过
if (Graph[Graph_v_id][i] && !visted[i]) {
// 将当前的路径长度记录下来
maxroad += Graph[Graph_v_id][i];
if(realmaxroad < maxroad) {
realmaxroad = maxroad;
// 记录当前最远的这个点的id
second_v_id = i;
}
findroad(Graph, visted, i, maxroad);
// 路径长度回溯,记录其他支路计算
maxroad -= Graph[Graph_v_id][i];
}
}
}
int cost (int road) {
return (road * 10 + (road + 1) * road / 2);
}
因为我使用的是邻接矩阵的方式来存图,容易出现爆内存的情况,所以再改用邻接链表的方式来存储图,除此之外,极端情况下可以采用2倍边数的二维数组来存储所有的边,不过这样的话查找一条边是否存在的时候的时间复杂度会很高,如下:
// 开始使用边表建图,边一共有v_nums - 1条,这里为了方便
// 对于Graph[0],这是输入时,一条边的前顶点
// 对应的Graph[1],这里是输入时,一条边的后顶点
// 所以这里数组的下标和顶点顺序已经无关,查找一条边是否存在的时间复杂度最坏为O(n).
vector<vector<int> > Graph(2, vector<int>(v_nums - 1, 0));
在第二种采用邻接链表进行存储图,四个评测用例全通过。
// 采用邻接链表的方式来存储图
#include <iostream>
#include <vector>
#include <stdlib.h>
using namespace std;
// 邻接边表结构
typedef struct v_node {
int v_id;
int e_weight; // 顶点表中的那个顶点到这个顶点的距离
v_node * next;
}v_node;
// dfs找当前节点最长径
void findroad(vector<v_node*> & GraphNode, vector<int> & visted,\
int Graph_v_id, int maxroad);
// 旅费计算
int cost(int road);
// 建图
void buildGraph(vector<v_node*> & GraphNode, int v1, int v2, int v1tov2);
// 找v1到v2的边的长度,存在返回边长,不存在返回0
int findEdge(vector<v_node*> & GraphNode, int v1, int v2);
int realmaxroad = 0;
// 第一次dfs中最远的那个点
int second_v_id = 0;
int main() {
int v_nums;
// 保存图的顶点数
cin >> v_nums;
// 顶点数组(保存边表第一个指针就可以了)
vector<v_node*> GraphNode(v_nums + 1, NULL);
// 开始进行建图
// 每个顶点的遍历情况
vector<int> visted(v_nums + 1, 0);
int v1, v2, v1tov2;
// 记录v_nums - 1条路径情况
for (int i = 0; i < v_nums - 1; ++i) {
cin >> v1 >> v2 >> v1tov2;
buildGraph(GraphNode, v1, v2, v1tov2);
}
// 开始找到最长的一条路
int maxroad = 0;
// 第一次dfs,从首都开始查找
findroad(GraphNode, visted, 1, maxroad);
// 重新置0
std::fill(visted.begin(), visted.end(), 0);
maxroad = 0;
// 第二次dfs
findroad(GraphNode, visted, second_v_id, maxroad);
//cout << realmaxroad << endl;
// 费用计算
int maxcost = cost(realmaxroad);
cout << maxcost << endl;
return 0;
}
void findroad(vector<v_node*> & GraphNode, vector<int> & visted,\
int Graph_v_id, int maxroad) {
// 修改当前节点状态
visted[Graph_v_id] = 1;
// 找当前节点的邻接点
for(int i = 1; i < visted.size(); ++i) {
// 如果这两个城市之间能够直达,同时没有被遍历过
int v1tov2 = findEdge(GraphNode, Graph_v_id, i);
if (v1tov2 && !visted[i]) {
// 将当前的路径长度记录下来
maxroad += v1tov2;
if(realmaxroad < maxroad) {
realmaxroad = maxroad;
// 记录当前最远的这个点的id
second_v_id = i;
}
findroad(GraphNode, visted, i, maxroad);
// 路径长度回溯,记录其他支路计算
maxroad -= v1tov2;
}
}
}
int cost (int road) {
return (road * 10 + (road + 1) * road / 2);
}
void buildGraph(vector<v_node*> & GraphNode, int v1, int v2, int v1tov2) {
// 如果是第一个
if(!GraphNode[v1]) {
v_node* node = (v_node*)malloc(sizeof(v_node));
node->v_id = v2;
node->e_weight = v1tov2;
node->next = NULL;
// 链到顶点数组上去
GraphNode[v1] = node;
}
// 如果不是第一个,加到最后一个next上去
else {
v_node* oldnode = GraphNode[v1];
while(oldnode->next){
oldnode = oldnode->next;
}
v_node* node = (v_node*)malloc(sizeof(v_node));
node->v_id = v2;
node->e_weight = v1tov2;
node->next = NULL;
oldnode->next = node;
}
// 同理v2也要这样处理
// 如果是v2的第一个
if(!GraphNode[v2]) {
v_node* node = (v_node*)malloc(sizeof(v_node));
node->v_id = v1;
node->e_weight = v1tov2;
node->next = NULL;
// 链到顶点数组上去
GraphNode[v2] = node;
}
// 如果不是第一个,加到最后一个next上去
else {
v_node* oldnode = GraphNode[v2];
while(oldnode->next){
oldnode = oldnode->next;
}
v_node* node = (v_node*)malloc(sizeof(v_node));
node->v_id = v1;
node->e_weight = v1tov2;
node->next = NULL;
oldnode->next = node;
}
}
int findEdge(vector<v_node*> & GraphNode, int v1, int v2) {
if (!GraphNode[v1]) {
return 0;
} else {
v_node * node = GraphNode[v1];
do {
if (node->v_id == v2) {
return node->e_weight;
}
node = node->next;
}
while (node);
}
return 0;
}
- 第三个版本:树形DP,做不来了。
参考目录
- 树的直径