题解:洛谷 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

【核心思想】

  1. 问题分析:Terry和Jom在有根无向图上博弈,Terry先手,两人轮流移动或睡觉,Terry不能走到Jom所在的结点。给定 \(q\) 次询问,每次询问Terry能否从起点 \(a\) 到达根节点 \(r\)。这是一个BFS + 博弈论问题,关键在于发现胜负条件只与双方到根节点的距离有关。

  2. 算法选择

    • BFS求最短距离:从根节点 \(r\) 出发,使用BFS计算每个节点到根的最短距离
    • 博弈论分析:Terry能到达根的条件是 dist[a] <= dist[b]
      • \(dist[a] \leq dist[b]\):Terry可以先到达根节点,Jom无法拦截
      • \(dist[a] > dist[b]\):Jom可以先到达根节点或拦截Terry的路径
  3. 关键步骤

    • 建图:读取 \(n\)(点数)、\(m\)(边数)、\(r\)(根节点),使用邻接表存储无向图
    • BFS计算距离:从根节点 \(r\) 开始BFS,计算 dist[i] 表示节点 \(i\) 到根的最短距离
    • 处理查询
      • 读取 \(q\)(查询次数)
      • 对于每次查询 \((a, b)\)
        • dist[a] <= dist[b],输出 Terry
        • 否则输出 Jom
  4. 时间/空间复杂度

    • 时间复杂度:\(O(n + m + q)\),BFS \(O(n + m)\),每次查询 \(O(1)\)
    • 空间复杂度:\(O(n + m)\),邻接表和距离数组
  5. 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
posted @ 2026-06-14 10:48  团爸讲算法  阅读(5)  评论(0)    收藏  举报