1 条题解

  • 0
    @ 2025-11-5 20:03:36

    您指出的问题非常对,我之前的回答中确实还有一部分数字和公式没有用 $ 符号包裹。这是我的疏忽,非常抱歉。我将严格按照要求,修正所有格式问题。


    题目理解

    我们有一个机器人,初始在 11 号城市,每秒可以做三种行为之一:

    1. 停留在当前城市(自环) 2. 走向相邻城市(有边相连) 3. 自爆(进入一个特殊状态,之后一直停留)

    求经过 tt 秒后,所有可能的行为序列(即状态转移路径)总数,对 20172017 取模。


    核心思路

    这个问题可以转化为 图上的带自环与自爆状态的步数计数问题

    1. 状态定义

    dp[s][i]dp[s][i] 表示经过 ss 秒后位于节点 ii 的方案数。

    初始状态:dp[0][1]=1dp[0][1] = 1,其余为 00

    2. 状态转移

    对于每个城市 ii

    • 可以停留在自身(自环)
    • 可以走到相邻城市(邻接边)
    • 可以自爆(进入一个特殊节点 00

    自爆状态 00 是一个吸收态,只能停留在自身。

    因此转移方程为: dp[s+1][j]=i=0Ndp[s][i]Ai,jdp[s+1][j] = \sum_{i=0}^{N} dp[s][i] \cdot A_{i,j}

    其中 AA 是转移矩阵,Ai,j=1A_{i,j} = 1 表示可以从 iijj


    3. 矩阵表示

    我们构建一个大小为 (N+1)×(N+1)(N+1) \times (N+1) 的矩阵 mp\text{mp},下标 00 表示自爆状态,1N1 \sim N 表示城市。

    矩阵构造规则:

    1. 邻接边:如果原图有边 uvu \leftrightarrow v,则 mp.a[u][v]=1\text{mp.a}[u][v] = 1mp.a[v][u]=1\text{mp.a}[v][u] = 1
    2. 自环mp.a[i][i]=1\text{mp.a}[i][i] = 1(每个城市和自爆状态都可以停留)。
    3. 自爆mp.a[i][0]=1\text{mp.a}[i][0] = 1(每个城市都可以自爆到状态 00)。

    这样,矩阵 AAkk 次幂 AkA^k 中的元素 (Ak)i,j(A^k)_{i,j} 就表示从 iijj 经过 kk 步的路径数。


    4. 答案计算

    初始时我们在城市 11,所以看 AtA^t 的第 11 行(代码中下标为 11)的所有元素之和:

    ans=j=0N(At)1,j\text{ans} = \sum_{j=0}^{N} (A^t)_{1,j}

    因为最终可以停留在任意城市或自爆状态,都算作不同的行为序列。


    代码解析

    #include <cstdio>
    #include <cstring>
    #include <cstdlib>
    #include <cctype>
    #include <cmath>
    #include <iostream>
    #include <algorithm>
    #include <vector>
    #include <set>
    #include <map>
    #include <queue>
    #include <stack>
    typedef long long ll;
    typedef unsigned long long ull;
    using namespace std;
    const int P = 2017;
    
    struct Matrix {
        int a[31][31];  // $0$~$30$ 共 $31$ 个节点,$0$ 号是自爆状态
        inline Matrix operator * (const Matrix &rhs) {
            Matrix ret;
            memset(&ret, 0, sizeof ret);
            for (int i = 0; i <= 30; i++)
                for (int j = 0; j <= 30; j++)
                    for (int k = 0; k <= 30; k++)
                        (ret.a[i][j] += a[i][k] * rhs.a[k][j] % P) %= P;
            return ret;
        }
    } mp;
    
    Matrix ksm(Matrix &a, int b) {
        Matrix ret;
        memset(&ret, 0, sizeof ret);
        for (int i = 0; i <= 30; i++)
            ret.a[i][i] = 1;  // 单位矩阵
        while (b) {
            if (b & 1)
                ret = ret * a;
            a = a * a;
            b >>= 1;
        }
        return ret;
    }
    
    int u, v, n, m, t;
    int main() {
        scanf("%d%d", &n, &m);
        for (int i = 1; i <= m; i++) {
            scanf("%d%d", &u, &v);
            mp.a[u][v] = 1;
            mp.a[v][u] = 1;  // 无向图邻接矩阵
        }
        for (int i = 0; i <= n; i++)
            mp.a[i][i] = 1;  // 自环:每个节点可以停留
        for (int i = 1; i <= n; i++)
            mp.a[i][0] = 1;  // 自爆:每个城市可以进入状态 $0$
    
        int ans = 0;
        scanf("%d", &t);
        Matrix anss = ksm(mp, t);  // 计算 $\text{mp}^t$
        for (int i = 0; i <= n; i++)
            (ans += anss.a[1][i]) %= P;  // 从 $1$ 出发,$t$ 秒后到任意状态 ($0$~$n$) 的方案数之和
        printf("%d\n", ans);
    }
    

    样例验证

    样例输入:

    3 2
    1 2
    2 3
    2
    

    构造的矩阵(030 \sim 344 个节点):

    $$A = \begin{bmatrix} 1 & 0 & 0 & 0 \\ 1 & 1 & 1 & 0 \\ 1 & 1 & 1 & 1 \\ 1 & 0 & 1 & 1 \end{bmatrix} $$

    计算 A2A^2 得到:

    $$A^2 = \begin{bmatrix} 1 & 0 & 0 & 0 \\ 3 & 2 & 2 & 1 \\ 4 & 2 & 3 & 2 \\ 3 & 1 & 2 & 2 \end{bmatrix} $$

    第一行(对应节点 11)的和 =3+2+2+1=8= 3 + 2 + 2 + 1 = 8,与样例输出一致。


    复杂度分析

    • 矩阵大小:(n+1)×(n+1)(n+1) \times (n+1),最大 31×3131 \times 31
    • 矩阵乘法:O(n3)O(n^3)
    • 快速幂:O(logt)O(\log t)
    • 总复杂度:O(n3logt)O(n^3 \log t),在 n30n \leq 30t106t \leq 10^6 时非常快。

    总结

    这道题的关键在于:

    1. 将三种行为统一建模为图上的边(邻接边、自环、自爆边)
    2. 引入自爆状态 00 作为吸收态
    3. 使用矩阵快速幂计算固定步数后的路径总数
    4. 答案 == 从起点到所有状态(包括自爆)的路径数之和

    这个技巧广泛应用于计数类路径问题,特别是步数很大的情况。

    • 1

    信息

    ID
    5022
    时间
    1000ms
    内存
    256MiB
    难度
    10
    标签
    递交数
    1
    已通过
    1
    上传者