题解:洛谷 P2233 公交车路线

【题目来源】

洛谷:P2233 [HNOI2002] 公交车路线

【题目描述】

在长沙城新建的环城公路上一共有 \(8\) 个公交站,分别为 A、B、C、D、E、F、G、H。公共汽车只能够在相邻的两个公交站之间运行,因此你从某一个公交站到另外一个公交站往往要换几次车,例如从公交站 A 到公交站 D,你就至少需要换 \(3\) 次车。

img/Pasted image 20260809205402.png

Tiger 的方向感极其糟糕,我们知道从公交站 A 到公交 E 只需要换 \(4\) 次车就可以到达,可是 tiger 却总共换了 \(n\) 次车,注意 tiger 一旦到达公交站 E,他不会愚蠢到再去换车。现在希望你计算一下 tiger 有多少种可能的乘车方案。

【输入】

仅有一个正整数 \(n\),表示 tiger 从公交车站 A 到公交车站 E 共换了 \(n\) 次车。

【输出】

输出一个正整数表示方案数,由于方案数很大,请输出方案数除以 \(1000\) 后的余数。

【输入样例】

6

【输出样例】

8

【核心思想】

  1. 问题分析:给定 \(8\) 个公交站环形排列(A~H,编号 \(0\)~\(7\)),相邻站之间有直达路线。Tiger 从 A 站(编号 \(0\))出发,恰好换乘 \(n\) 次后到达 E 站(编号 \(4\)),且一旦到达 E 站就不再换乘。求方案数对 \(1000\) 取模。这是一个线性 DP 问题,核心在于状态转移时排除从 E 站出发的路径(吸收态)。

  2. 算法选择

    • 一维线性 DP(滚动数组)\(f[i][j]\) 表示换乘 \(i\) 次后到达第 \(j\) 个站的方案数
    • 环形邻接:每个站 \(j\) 的相邻站为 \((j-1+8) \% 8\)\((j+1) \% 8\),A 和 H 也相邻
    • 吸收态处理:到达 E 站(编号 \(4\))后停止,因此 E 站不能作为转移来源
  3. 关键步骤

    • 初始化\(f[0][0] = 1\)(换乘 \(0\) 次在 A 站,\(1\) 种方案),其余为 \(0\)
    • DP 递推\(i\)\(1\)\(n\)):
      • 使用滚动数组 \(cur = i \% 2\)\(pre = (i-1) \% 2\)
      • 遍历当前站 \(j\)\(0\)\(7\)):
        • 计算左右邻站:\(left = (j-1+8) \% 8\)\(right = (j+1) \% 8\)
        • 状态转移\(f[cur][j] = (f[pre][left] \cdot [left \neq 4] + f[pre][right] \cdot [right \neq 4]) \bmod 1000\)
        • 其中 \([condition]\) 为指示函数,排除从 E 站转移来的路径
    • 输出答案\(f[n \% 2][4]\)(换乘 \(n\) 次后到达 E 站的方案数)
  4. 时间/空间复杂度

    • 时间复杂度:\(O(n \times 8) = O(n)\),每次换乘枚举 \(8\) 个站,每个站 \(O(1)\) 转移
    • 空间复杂度:\(O(8) = O(1)\),滚动数组仅维护两行
  5. 线性 DP 的核心思想

    • 状态定义清晰\(f[i][j]\) 精确刻画"换乘 \(i\) 次后在 \(j\) 站"的方案数,满足无后效性
    • 吸收态建模:E 站作为终点,到达后不再离开,通过禁止从 E 站向其他站转移实现,而非将 E 站方案数清零(因为需要统计最终到达 E 站的方案)
    • 环形结构处理:取模运算 \((j \pm 1 + 8) \% 8\) 优雅处理 A-H 的环形邻接
    • 滚动数组优化:由于 \(f[i]\) 仅依赖 \(f[i-1]\),用两行数组交替使用,将空间从 \(O(n)\) 降至 \(O(1)\)
    • 适用于环形图上的路径计数、带吸收态的随机游走、有限状态转移类问题

【算法标签】

普及+ #线性DP-一维

【代码详解】

#include <bits/stdc++.h>
using namespace std;
const int N = 10000005, mod = 1000; // N:最大换乘次数上限, mod:取模基数
int f[2][8]; // 滚动数组:f[cur][j]表示换乘i次后到达第j个公交站(0=A,1=B,...,4=E,...,7=H)的方案数
int n; // tiger实际换乘的次数

int main()
{
    cin >> n; // 读入换乘次数
    f[0][0] = 1; // 初始状态:换乘0次时在A站(编号0),方案数为1
    for (int i=1; i<=n; i++) // 外层循环:枚举每次换乘
    {
        int cur = i % 2; // 当前轮次的数组下标(滚动数组优化空间)
        int pre = (i-1) % 2; // 上一轮次的数组下标
        for (int j = 0; j<8; j++) // 内层循环:枚举当前所在的公交站(0~7对应A~H)
        {
            // 计算当前站j的左右相邻站(环形结构:A和H也相邻)
            int left = (j-1+8)%8, right = (j+1)%8;
            int sum = 0; // 累加到达当前站的方案数
            // 可以从左邻站到达当前站,但不能从E站(编号4)转移(到达E就停止)
            if (left!=4) sum += f[pre][left];
            // 可以从右邻站到达当前站,同样不能从E站转移
            if (right!=4) sum += f[pre][right];
            f[cur][j] = sum % mod; // 对1000取模存储
        }
    }
    // 输出换乘n次后到达E站(编号4)的方案数
    cout << f[n%2][4] << endl;
    return 0;
}

【运行结果】

6
8
posted @ 2026-08-10 12:28  团爸讲算法  阅读(11)  评论(0)    收藏  举报