题目背景
在《三国演义》中,诸葛亮以其卓越的智慧和深思熟虑的战略而著称。某日,诸葛亮在蜀汉准备重要军事行动时,为了确保信息安全,他将一份机密文件放到一个密码箱里面,并设置了一道谜题,只有解出谜题才能知道密码。
题目描述
诸葛亮 有一棵有 n 个顶点的树。初始时,所有顶点都是白色的。
树上有两颗棋子,分别叫做 PA 和 PB 。 PA和 PB 最初分别位于顶点 a 和 b 。在一个步骤中,诸葛亮 将依次执行以下操作:
- 将 PA 移动到相邻顶点。如果目标顶点是白色的,这个顶点将被涂成红色。
- 将 PB 移动到相邻顶点。如果目标顶点是红色的,这个顶点将被涂成蓝色。
最初,顶点 aa 会被涂成红色。如果是 a=b ,顶点 a 将被涂成蓝色。请注意,每一步都必须移动两个棋子。任何时候都可以有两个棋子位于同一个顶点上。
将所有顶点都涂成蓝色所需的最少步数即为密码箱的密码。
输入格式
每个测试包含多个测试用例。测试用例说明如下:
-
第一行包含测试用例的数量 t (1≤t≤20)
-
每个测试用例的第一行包含一个整数 n (1≤n≤100)
-
每个测试用例的第二行包含两个整数 a 和 b (1≤a,b≤n)
-
然后是 n−1 行,每行包含两个整数 xi和 yi (1≤xi,yi≤n),表示顶点 xi 和 yi 之间有一条边。可以保证这些边构成一棵树。
保证所有测试用例中 n 的总和不超过 2⋅10次方 。
输出格式
对于每个测试用例,输出将所有顶点涂成蓝色所需的最少步数。
输入输出样例
输入 #1复制
3 2 1 2 1 2 5 1 2 1 2 1 3 1 4 1 5 8 5 4 7 1 1 5 1 8 8 3 7 2 8 6 3 4
输出 #1复制
2 8 13
说明/提示
在第一个测试案例中,诸葛亮 可以按以下顺序将所有顶点涂成蓝色:
最初, PA位于顶点 1 上, PB 位于顶点 2 上。顶点 1 涂成红色,顶点 2 涂成白色。诸葛亮 移动 PA 至顶点 2 并将其涂成红色。然后 诸葛亮 将 PB 移至顶点 1 ,并涂上蓝色。诸葛亮 移动 PA到顶点 1 。然后 诸葛亮 移动 PB 至顶点 2 并涂上蓝色。
步骤一:
按照题目所说,我们需要构建一个无向树,我么可以利用链表构成树。
代码如下:
struct Node {
int data;
Node *next;
};
const int N = 1e4+5;
Node *List[N]; // 邻接表,大小设为50001以容纳最多50000个节点(从1到50000)
void init()
{
memset(List, 0, sizeof(List)); // 初始化邻接表为NULL
}
void add(int src, int dest) {
Node *newnode_src = new Node();
newnode_src->data = dest;
newnode_src->next = List[src];
List[src] = newnode_src;
Node *newnode_dest = new Node();
newnode_dest->data = src;
newnode_dest->next = List[dest];
List[dest] = newnode_dest;
}
步骤二:
1.题目要求的是(对于每个测试用例,输出将所有顶点涂成蓝色所需的最少步数),所以我们并且只B点触碰到A点涂上红色的点,才算开始涂蓝色。所以需要一种思想,就是尽快的让A,B相遇,这也是B点最快触碰红色点并涂上蓝色。
2.我们需要知道相遇点的具体位置,所以我们需要用dfs来深搜并且储存路径。
代码如下:
bool distant(int current, int target)
{
Path.push_back(current); // 将当前节点加入路径
visited[current] = true; // 标记当前节点为已访问
if (current == target) // 找到目标节点
{
return true; // 返回true表示找到路径
}
Node *temp = List[current];
while (temp)
{
int adjNode = temp->data;
if (!visited[adjNode])
{
if (distant(adjNode, target)) // 递归搜索相邻节点
{
return true; // 如果在相邻节点中找到路径,则返回true
}
}
temp = temp->next;
}
Path.pop_back(); // 如果在当前分支没有找到路径,则回溯
return false; // 返回false表示在当前分支没有找到路径
}
步骤三:
1.题目要求的是到达终点不返回,也是意味着我们可以选取相遇点作为起点,进行一个dfs,寻找离这个起点最远距离的节点。因为我们并不想往返这跳最长的路。
代码如下:
void longest(int current, int parent, int step)
{
if (step > M)
{
M = step;
// cout << "最远距离更新:" << M << " 节点:"<< farthest_node << endl;
farthest_node = current;
}
Node *temp = List[current];//设置一个指针指向这个节点
while (temp)
{
if (temp->data != parent && !vislong[temp->data]) // 避免回到父节点,并且没有访问过
{
vislong[temp->data] = true; // 标记为已访问
longest(temp->data, current, step + 1); //传递参数为,当前下标,当前节点下标
}
temp = temp->next;//指向下一个节点
}
}
步骤四:
我们发现无向图每两个点之间有两条边,进行遍历图的时候,我们需要往返,也意味着每跳边都要走(n-1)* 2,但是我们所求出最长的那条边只要走一次。所以目前步数是(n-1)*2 - M。我们需要再加上相遇时候B点走的步数,由于我们已经储存过相遇时候的路径,所以我们可以计算出相遇B的步数。所以最短步数就是2*(n-1)-M+len/2
总代码如下;
#include <iostream>
#include <vector>
#include <cstring>
using namespace std;
struct Node {
int data;
Node *next;
};
const int N = 1e4+5;
Node *List[N]; // 邻接表,大小设为50001以容纳最多50000个节点(从1到50000)
int T; // 测试用例数量
int n; // 树的节点数量
int a, b; // 起始节点和目标节点
int x, y; // 用于读取边的输入
bool visited[N]; // 访问标记数组
bool vislong[N];
vector<int> Path; // 用于存储找到的路径
int M = -1e6;
int farthest_node = -1; // 用于存储最远节点的下标
int ans[N];
void init()
{
memset(List, 0, sizeof(List)); // 初始化邻接表为NULL
memset(visited, 0, sizeof(visited)); // 重置访问标记数组
Path.clear(); // 清空路径向量
memset(vislong, 0, sizeof(vislong)); // 重置访问标记数组
M = -1e6;
farthest_node = -1;
}
void add(int src, int dest) {
Node *newnode_src = new Node();
newnode_src->data = dest;
newnode_src->next = List[src];
List[src] = newnode_src;
Node *newnode_dest = new Node();
newnode_dest->data = src;
newnode_dest->next = List[dest];
List[dest] = newnode_dest;
}
bool distant(int current, int target)
{
Path.push_back(current); // 将当前节点加入路径
visited[current] = true; // 标记当前节点为已访问
if (current == target) // 找到目标节点
{
return true; // 返回true表示找到路径
}
Node *temp = List[current];
while (temp)
{
int adjNode = temp->data;
if (!visited[adjNode])
{
if (distant(adjNode, target)) // 递归搜索相邻节点
{
return true; // 如果在相邻节点中找到路径,则返回true
}
}
temp = temp->next;
}
Path.pop_back(); // 如果在当前分支没有找到路径,则回溯
return false; // 返回false表示在当前分支没有找到路径
}
void check(bool found)
{
if(found) // 查找从a到b的路径
{
cout << "Path from " << a << " to " << b << ": ";
for (int node : Path)
{
cout << node << " ";
}
cout << endl;
}
else
{
cout << "No path found from " << a << " to " << b << endl; // 理论上在树中不应该发生,除非a或b不是树的一部分
}
}
void longest(int current, int parent, int step)
{
if (step > M)
{
M = step;
// cout << "最远距离更新:" << M << " 节点:"<< farthest_node << endl;
farthest_node = current;
}
Node *temp = List[current];//设置一个指针指向这个节点
while (temp)
{
if (temp->data != parent && !vislong[temp->data]) // 避免回到父节点,并且没有访问过
{
vislong[temp->data] = true; // 标记为已访问
longest(temp->data, current, step + 1); //传递参数为,当前下标,当前节点下标
}
temp = temp->next;//指向下一个节点
}
}
int main()
{
cin >> T;
for (int t = 1; t <= T; t++)
{
int meeting;
init(); // 初始化每次查询前的状态
cin >> n;
cin >> a >> b;
for (int i = 0; i < n - 1; i++) // 读取n-1条边(因为是无向树)
{
cin >> x >> y;
add(x, y); // 添加边到图中
}
bool F = distant(a,b);
// check(F);
int len = Path.size();
meeting = Path[(len-1)/2];//下标从0开始,相遇点奇偶性取整都是len/2
// cout << "以B为基准的相遇下标:" << meeting << endl;
longest(meeting, -1, 0); // -1 表示没有父节点,从 meeting 出发,步数为 0
ans[t] = 2*(n-1)-M+len/2;
}
for(int i = 1 ; i <= T ; i++)
{
cout << ans[i] << endl;
}
return 0;
}
标签:src,temp,int,密码箱,诸葛亮,newnode,顶点,U504405,节点
From: https://blog.csdn.net/zqystca/article/details/144570149