首页 > 其他分享 >U504405 破译诸葛亮的密码箱

U504405 破译诸葛亮的密码箱

时间:2024-12-19 21:29:42浏览次数:11  
标签:src temp int 密码箱 诸葛亮 newnode 顶点 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;
}

标签:src,temp,int,密码箱,诸葛亮,newnode,顶点,U504405,节点
From: https://blog.csdn.net/zqystca/article/details/144570149

相关文章

  • 事后诸葛亮分析
    设想和目标1. 我们的软件要解决什么问题?是否定义得很清楚?是否对典型用户和典型场景有清晰的描述?我们的软件主要解决学生想要交易二手物品的时候难以找到买家,以及难以买到想要的二手物品,提供一个平台给有需求的人去买卖二手物品;定义得很清楚;典型用户是学生,典型场景是想要交易二......
  • 事后诸葛亮分析
    事后诸葛亮分析1.设想和目标我们的软件要解决什么问题?是否定义得很清楚?是否对典型用户和典型场景有清晰的描述?本团队希望做出一个简易记账工具,能够对自己的收入和支出有一个明确的分类以及总结,对自己的收支比较了解。定义得比较清楚,典型用户也比较清晰。是否有充足的时......
  • 事后诸葛亮分析报告
    事后诸葛亮分析报告一、项目总结与反思我们的软件要解决什么问题?是否定义得很清楚?我们的软件主要服务于对我们学校感兴趣的人群,为他们提供了解渠道,保证真实性以及用户评价,定义很清楚用户量,用户对重要功能的接受程度和我们事先的预想一致么?我们离目标更近了么?用户量在Alpha......
  • 事后诸葛亮分析报告
    设想和目标###1.我们的软件要解决什么问题?是否定义得很清楚?是否对典型用户和典型场景有清晰的描述?主要解决了多种功能集成和高并发的问题项目的典型用户包括教师、学生以及系统管理员,场景涵盖了信息管理、成绩管理等。对于这些场景,目标清晰。2.我们达到目标了么(原计划的功能做......
  • 事后诸葛亮分析
    这个作业属于哪个课程计科22级12班这个作业要求在哪里团队作业6——复审与事后分析这个作业的目标事后诸葛亮分析一、设想和目标1.1我们的软件要解决什么问题?是否定义得很清楚?是否对典型用户和典型场景有清晰的描述?我们的软件旨在解决行人跌倒检测的问题,通过......
  • 事后诸葛亮分析报告
    这个作业属于哪个课程广工计院计科34班软工这个作业要求在哪里作业要求[https://edu.cnblogs.com/campus/gdgy/CSGrade22-34/homework/13236]这个作业的目标复审与事后分析姓名学号罗祖文3121004537郑志涛3122004547陈恺麟3122004515许凌......
  • 事后诸葛亮分析报告
    一、项目总结与反思1.我们的软件要解决什么问题?是否定义得很清楚?我们软件主要解决校内人士在拾取到贵重物品时难以寻得失主的问题。我们对该项目有着清楚的定义,专注于失物招领功能2.用户量,用户对重要功能的接受程度和我们事先的预想一致么?我们离目标更近了么?用户量在Alpha......
  • 事后诸葛亮分析
    事后诸葛亮分析目录一、设想和目标二、计划三、资源四、变更管理五、设计/实现六、测试/发布七、总结一、设想和目标1.1我们的软件要解决什么问题?是否定义得很清楚?是否对典型用户和典型场景有清晰的描述?软件要解决的问题及相关分析1.问题阐述选课效率与公......
  • 事后诸葛亮分析
    课程2024软件工程作业要求团队作业6——复审与事后分析作业目标事后诸葛亮分析会议合照回顾与反思Q:我们的软件要解决什么问题?A:我们的软件是一个高校贴吧,集成水电,快递,外卖等功能Q:我们达到目标了吗?A:达成一半,集成功能仍然有bug需要修理Q:我们是否有充足的时......
  • 事后诸葛亮分析
    1.设想和目标我们的软件要解决什么问题?是否定义得很清楚?是否对典型用户和典型场景有清晰的描述?软件旨在解决教育管理中的效率问题,包括教师端的班级管理、作业发布与成绩管理,以及学生端的作业提交与成绩查询。问题定义清晰,典型用户(教师和学生)和场景(教育管理)有明确描述。我......