题解:洛谷 P11280 Jom & Terry
【题目来源】
洛谷:P11280 「GFOI Round 2」Jom & Terry - 洛谷
【题目描述】
Terry 和 Jom 在一个 \(n\) 个点 \(m\) 条边的有“根”无向连通图上博弈(图的根为 \(r\)),遵循以下规则:
- Terry 先手;
- 两人轮流在图上移动,每次只能走一条边(也可以睡觉,啥都不干);
- Terry 不能走到 Jom 所在的结点(我们认为只有 Terry 自投罗网时才会被抓到,即如果 Terry 先移动到结点 \(u\) 后 Jom 在同一回合也移动到 \(u\) 是合法的)。
给定 \(q\) 次询问,每次询问给定 Terry 和 Jom 的起点 \(a, b\),你需要回答 Terry 能否到达根(即点 \(r\))。
【输入】
第一行包含三个整数 \(n, m, r\),表示点数、边数和根的编号;
接下来 \(m\) 行,每行包含两个整数 \((u, v)\) 表示一条边(注意可能存在重边或自环);
接下来一行包含一个整数 \(q\),表示询问数;
接下来 \(q\) 行,每行包含两个整数 \(a, b\),表示 Terry 和 Jom 的起点。
【输出】
因为这是签到题,所以你应该在开头输出 I'm here!。
接下来 \(q\) 行的第 \(i\) 行,如果在第 \(i\) 次询问中 Terry 能到达根就输出 Terry,否则输出 Jom。
【输入样例】
5 4 3
4 3
3 2
1 5
1 2
2
1 2
5 4
【输出样例】
I'm here!
Jom
Jom
【核心思想】
-
问题分析:Terry和Jom在有根无向图上博弈,Terry先手,两人轮流移动或睡觉,Terry不能走到Jom所在的结点。给定 \(q\) 次询问,每次询问Terry能否从起点 \(a\) 到达根节点 \(r\)。这是一个BFS + 博弈论问题,关键在于发现胜负条件只与双方到根节点的距离有关。
-
算法选择:
- BFS求最短距离:从根节点 \(r\) 出发,使用BFS计算每个节点到根的最短距离
- 博弈论分析:Terry能到达根的条件是
dist[a] <= dist[b]- 若 \(dist[a] \leq dist[b]\):Terry可以先到达根节点,Jom无法拦截
- 若 \(dist[a] > dist[b]\):Jom可以先到达根节点或拦截Terry的路径
-
关键步骤:
- 建图:读取 \(n\)(点数)、\(m\)(边数)、\(r\)(根节点),使用邻接表存储无向图
- BFS计算距离:从根节点 \(r\) 开始BFS,计算
dist[i]表示节点 \(i\) 到根的最短距离 - 处理查询:
- 读取 \(q\)(查询次数)
- 对于每次查询 \((a, b)\):
- 若
dist[a] <= dist[b],输出Terry - 否则输出
Jom
- 若
-
时间/空间复杂度:
- 时间复杂度:\(O(n + m + q)\),BFS \(O(n + m)\),每次查询 \(O(1)\)
- 空间复杂度:\(O(n + m)\),邻接表和距离数组
-
BFS + 博弈论的核心思想:
- 距离决定胜负:在追逐博弈中,距离目标更近的一方通常具有优势
- BFS预处理:一次性计算所有节点到根的距离,查询时 \(O(1)\) 回答
- 博弈策略简化:复杂的博弈过程可以简化为距离比较
- 适用于图上的追逐博弈、最短路径相关博弈问题
【算法标签】
普及 #BFS-图
【代码详解】
#include <bits/stdc++.h>
using namespace std;
const int N = 1000005, M = N * 2; // 定义最大节点数和边数
int n, m, r, q; // 节点数n,边数m,根节点r,查询次数q
int h[N], e[M], ne[M], idx; // 邻接表头数组h,边数组e,下一邻接边数组ne,边索引idx
int dist[N]; // 存储从根节点到每个节点的距离
void add(int a, int b) // 添加边的函数
{
e[idx] = b, ne[idx] = h[a], h[a] = idx++; // 将边添加到邻接表中
}
void bfs() // 广度优先搜索求最短距离
{
memset(dist, 0x3f, sizeof(dist)); // 初始化所有距离为无穷大
queue<int> q; // 广度优先搜索队列
dist[r] = 0; // 根节点到自身的距离为0
q.push(r); // 根节点入队
while (!q.empty()) // 当队列不为空时循环
{
int u = q.front(); q.pop(); // 取出队首节点
// cout << "u " << u << endl; // 调试输出当前节点
for (int i=h[u]; i!=-1; i=ne[i]) // 遍历节点的所有邻接边
{
int v = e[i]; // 相邻节点
// cout << "v " << v << endl; // 调试输出相邻节点
if (dist[v]!=0x3f3f3f3f) continue; // 如果节点已访问过则跳过
if (dist[v]>dist[u]+1) // 如果找到更短路径
{
dist[v] = dist[u] + 1; // 更新距离
q.push(v); // 相邻节点入队
}
}
}
}
int main() // 主函数
{
scanf("%d%d%d", &n, &m, &r); // 输入节点数、边数和根节点
memset(h, -1, sizeof(h)); // 初始化邻接表头数组为-1
while (m--) // 读入所有边
{
int u, v; // 边的两个端点
scanf("%d%d", &u, &v); // 输入边的端点
add(u, v), add(v, u); // 添加无向边
}
bfs(); // 执行广度优先搜索计算距离
scanf("%d", &q); // 输入查询次数
printf("I'm here!\n"); // 调试输出
// for (int i=1; i<=n; i++) // 调试输出所有距离
// cout << dist[i] << " ";
// cout << endl;
while (q--) // 处理每个查询
{
int a, b; // 查询的两个节点
scanf("%d%d", &a, &b); // 输入查询的节点
if (dist[a]<=dist[b]) // 如果节点a到根节点的距离小于等于节点b的距离
printf("Terry\n"); // 输出Terry
else // 否则
printf("Jom\n"); // 输出Jom
}
return 0; // 程序正常结束
}
【运行结果】
5 4 3
4 3
3 2
1 5
1 2
2
I'm here!
1 2
Jom
5 4
Jom
浙公网安备 33010602011771号