Bootstrap

U504405 破译诸葛亮的密码箱

题目背景

在《三国演义》中,诸葛亮以其卓越的智慧和深思熟虑的战略而著称。某日,诸葛亮在蜀汉准备重要军事行动时,为了确保信息安全,他将一份机密文件放到一个密码箱里面,并设置了一道谜题,只有解出谜题才能知道密码。

题目描述

诸葛亮 有一棵有 n 个顶点的树。初始时,所有顶点都是白色的。

树上有两颗棋子,分别叫做 PA​ 和 PB​ 。 PA和 PB​ 最初分别位于顶点 a 和 b 。在一个步骤中,诸葛亮 将依次执行以下操作:

  1. 将 PA 移动到相邻顶点。如果目标顶点是白色的,这个顶点将被涂成红色。
  2. 将 PB​ 移动到相邻顶点。如果目标顶点是红色的,这个顶点将被涂成蓝色。

最初,顶点 aa 会被涂成红色。如果是 a=b ,顶点 a 将被涂成蓝色。请注意,每一步都必须移动两个棋子。任何时候都可以有两个棋子位于同一个顶点上

将所有顶点都涂成蓝色所需的最少步数即为密码箱的密码。

输入格式

每个测试包含多个测试用例。测试用例说明如下:

  1. 第一行包含测试用例的数量 t (1≤t≤20)

  2. 每个测试用例的第一行包含一个整数 n (1≤n≤100)

  3. 每个测试用例的第二行包含两个整数 a 和 b (1≤a,b≤n)

  4. 然后是 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;
}

;