• P4159 [SCOI2009] 迷路


    \(P4159\) [\(SCOI2009\)] 迷路

    题目传送门

    前序知识整理
    关键词:矩阵+快速幂
    P1226 【模板】快速幂||取余运算
    矩阵乘法
    P3390 【模板】矩阵快速幂
    P1939 【模板】矩阵加速(数列)

    一、题目描述

    题目背景
    \(windy\)有向图 中迷路了。

    题目描述
    该有向图有 \(n\) 个节点,节点从 \(1\)\(n\) 编号,\(windy\) 从节点 \(1\) 出发,他必须恰好在 \(t\) 时刻到达节点 \(n\)

    现在给出该有向图,你能告诉 \(windy\) 总共有多少种不同的路径吗?

    答案对 \(2009\) 取模。

    注意:\(windy\) 不能在某个节点逗留,且通过某有向边的时间严格为给定的时间。

    输入格式
    第一行包含两个整数,分别代表 \(n\)\(t\)

    \(2\) 到第 \((n+1)\) 行,每行一个长度为 \(n\) 的字符串,第 \((i + 1)\)行的第 \(j\) 个字符 \(c_{i, j}\)是一个数字字符,若为 \(0\),则代表节点 \(i\) 到节点 \(j\) 无边,否则代表节点 \(i\) 到节点 \(j\) 的边的长度为 \(c_{i, j}\)

    输出格式
    输出一行一个整数代表答案对 \(2009\) 取模的结果。

    二、题目解析

    第一反应:咦?这不是图论吗??? 默默的看了眼\(t\)的范围(\(<=1e9\)),死了心

    蒟蒻豆爸解释一下:
    为什么看一眼\(t\)的范围就知道不是图论呢?原因就是这个\(t\),一般图论就是用\(floyd,bellmanFord,spfa,dijkstra\)这些东东,而这些东东的时间复杂度最好也就是\(O((n+m)log⁡~m)\),一个\(m\)就干到了\(1e9\),不\(TLE\)就怪了

    \(floyd\) \(bellmanFord\) \(spfa\) \(dijkstra\)
    \(O(N^3)\) \(O(N\times M)\) \(O(N\times M)\) \(O((n+m)log⁡~m)\)

    \(DP\)!! \(DP\)一定可以!!!

    蒟蒻豆爸解释一下:
    \(DP\)能做到线性复杂度就足够牛\(X\)了吧,就算是线性的,也一样会\(TLE\),为啥呢?因为跑一遍所有边就是\(1e9\)~

    默默的看了眼\(t\)的范围,又死了心

    那么怎么做呢?

    一、边权为\(1\)的有向图中 两点间边权和 恰好是\(k\) 的路径条数

    首先,我们把这道题想简单一点,如果题目中的每一条边都没有边权,只用\(1\)\(0\)来表示两个点之间是否存在边,并且用邻接矩阵来存这张图,那么我们又可以得到些什么呢?

    举个栗子

    我们以下面这张图为例
    20221102111851

    以邻接矩阵来表示这个矩阵:
    \(\begin{bmatrix} 0& 1 & 1\\ 1&0 &1 \\ 1& 0 & 0 \end{bmatrix} \)矩阵\(1\)

    其中,\(a_{ij}\) 表示\(i\)\(j\)之间是否有连线;

    那么,我们把它 平方 一下,又可以得到什么呢?(友情提示:如果不清楚矩阵乘法,请点这里)

    \(\begin{bmatrix} 0& 1 & 1\\ 1&0 &1 \\ 1& 0 & 0 \end{bmatrix} \times \begin{bmatrix} 0& 1 & 1\\ 1&0 &1 \\ 1& 0 & 0 \end{bmatrix}= \begin{bmatrix} 2& 0 & 1\\ 1& 1 & 1 \\ 0& 1 & 1 \end{bmatrix} \)矩阵\(2\)

    你又发现了什么呢?

    好的,如果还没发现,我们再来将矩阵\(1\) 三次方一下:

    \(\begin{bmatrix} 0& 1 & 1\\ 1&0 &1 \\ 1& 0 & 0 \end{bmatrix} \times \begin{bmatrix} 0& 1 & 1\\ 1&0 &1 \\ 1& 0 & 0 \end{bmatrix} \times \begin{bmatrix} 0& 1 & 1\\ 1&0 &1 \\ 1& 0 & 0 \end{bmatrix}= \begin{bmatrix} 2& 0 & 1\\ 1& 1 & 1 \\ 0& 1 & 1 \end{bmatrix} \times \begin{bmatrix} 0& 1 & 1\\ 1&0 &1 \\ 1& 0 & 0 \end{bmatrix} = \begin{bmatrix} 1& 2 & 2\\ 2& 1 & 2 \\ 2& 0 & 1 \end{bmatrix} \) 矩阵\(3\)

    什么,你还没发现吗???

    那么让我来告诉你吧!!!

    矩阵\(1\),我们可以把\(a_{ij}\) 看成通过 一条边 ,由\(i\)到达\(j\)的情况总数
    矩阵\(2\),我们可以把\(a_{ij}\) 看成通过 两条边 ,由\(i\)到达\(j\)的情况总数
    矩阵\(3\),我们可以把\(a_{ij}\) 看成通过 三条边 ,由\(i\)到达\(j\)的情况总数

    不信?我们举个栗子:
    从点\(1\)到点\(1\),且通过 一条边 的情况不存在,记为\(0\)
    从点\(1\)到点\(1\),且通过 两条边 的情况共两种(\(1->2->1\) \(and\) \(1->3->1\)),记为\(2\)
    从点\(1\)到点\(1\),且通过 三条边 的情况仅有一种(\(1->2->3->1\)),记为\(1\)

    再回头看看矩阵吧!!!是不是完全满足这个条件呢???
    所以我们就可以得出结论啦:

    在矩阵\(A^x\)中,\(A^x_{ij}\)表示由\(i\)\(j\)经过\(x\)条边的情况总数

    所以这就可以运用 快速幂 啦!!!

    仔细算一下时间复杂度,\(O(n*logn)\),稳稳滴!!!

    那么,这道题就可以很快打出来啦——吗?

    显然是不可以的。

    可能你已经发现了,我们所有的推论都建立在边权为\(1\)的情况上,可是这道题目呢?

    接下来有 \(N\)行,每行一个长度为\(N\)的字符串。第\(i\)行第\(j\)列为 \(0\)表示从节点\(i\)到节点\(j\)没有边,为\(1\)\(9\)表示从节点\(i\)到节点\(j\)需要耗费的时间。

    呀呀呀,这道题目的边权不只是\(1\)呀!

    !(⊙ o ⊙)!

    怎么办呢?

    二、边权不为\(1\)的有向图中 两点间边权和 恰好是\(k\) 的路径条数

    虽然我们发现不能直接使用我们的结论,但是最大边权是\(9\)\(N\)也不超过\(10\)!都不算大!

    那我们就可以采用一种叫做 拆点 的方法:把 一个点拆成\(9\)个点(本质是按边权拆的)

    并且,我们发现即使如此拆点,\(N\)也不会超过\(100\),妥妥的可以呀!

    但怎么拆点呢?

    我们先来试一下拆一个边权不超过\(2\)的图吧!

    可得矩阵

    \[\large \begin{bmatrix} 0 & 2 \\ 2 & 1 \end{bmatrix}\]

    将其拆点:

    可得到新矩阵 :

    \[\large \begin{bmatrix} 0 & 1 & 0 & 0 \\ 0 & 0 & 1 & 0 \\ 0 & 0 & 1 & 1 \\ 1 & 0 & 0 & 0 \end{bmatrix}\]

    将其平方:

    \[\large \begin{bmatrix} 0 & 1 & 0 & 0 \\ 0 & 0 & 1 & 0 \\ 0 & 0 & 1 & 1 \\ 1 & 0 & 0 & 0 \end{bmatrix} \times \begin{bmatrix} 0 & 1 & 0 & 0 \\ 0 & 0 & 1 & 0 \\ 0 & 0 & 1 & 1 \\ 1 & 0 & 0 & 0 \end{bmatrix} = \begin{bmatrix} 0 & 0 & 1 & 0 \\ 0 & 0 & 1 & 1 \\ 1 & 0 & 1 & 1 \\ 0 & 1 & 0 & 0 \end{bmatrix} \]

    验算一下:

    原来有点\(1\)到点\(2\)并用经过\(2\)边权的方案总数有一种(\(1->2\),边权为\(2\));

    现在来说,点\(1\)变为点\(1'\),点\(2\)变为点\(3'\),经过\(2\)边权的方案总数依旧是\(2\)(\(1'->2'->3'\),边权均为\(1\));

    那么则说明我们的拆点是正确的。

    可以发现这样的话仍然是满足题意的

    这是为什么呢?为啥要这样拆点呢?

    因为可以更新答案的那条路径,一定走到了\(x_i\)号节点,正准备走到原来的另一个节点

    不是很好解释,自己多画几张图试试就知道了(我知道你们懒,给你们画一个)

    拆点 操作

    那么既然我们通过拆点操作将所有点之间的边权都变成了\(1\),那么我们就可以用刚才得到的 结论 啦!!!

    时间复杂度

    \(O(n^3\log ~t)\)

    实现代码

    #include <bits/stdc++.h>
    using namespace std;
    
    typedef long long LL;
    const int MOD = 2009;
    const int N = 110;
    
    int g[N][N], a[N][N];
    int n, t;
    
    //矩阵乘法
    void mul(int c[][N], int a[][N], int b[][N]) {
        int t[N][N] = {0};
        int M = n * 9;
        for (int i = 1; i <= M; i++) {
            for (int j = 1; j <= M; j++)
                for (int k = 1; k <= M; k++)
                    t[i][j] = (t[i][j] + (LL)(a[i][k] * b[k][j]) % MOD) % MOD;
        }
        memcpy(c, t, sizeof t);
    }
    
    int main() {
        // n个节点,t时刻
        scanf("%d %d", &n, &t);
    
        for (int i = 1; i <= n; i++) { //原图有n个节点
    
            /* 1点拆9点
              1-> 1~9
              2-> 10~18
              3-> 19~17
              ...
    
              拆开的点之间的权值是1
             */
            for (int j = 1; j < 9; j++) // 1点拆9点,注意收尾处是小于号
                g[(i - 1) * 9 + j][(i - 1) * 9 + j + 1] = 1;
    
            //从下标1开始读入原图中i号节点与其它各节点间的边权
            char s[11];
            scanf("%s", s + 1);
    
            //遍历一下输入的各点间关系图
            for (int j = 1; j <= n; j++)
                //如果i->j 存在边,并且边权 = s[j]
                //比如 1->8, 边权为3; 则 从3'->64'创建一条边权为1的边
                if (s[j] > '0') g[(i - 1) * 9 + s[j] - '0'][(j - 1) * 9 + 1] = 1;
        }
    
        //复制原始底图
        memcpy(a, g, sizeof g);
    
        //因为是复制出来,t次幂就变成了t-1次幂
        t--;
    
        //矩阵快速幂
        while (t) {
            if (t & 1) mul(g, g, a);
            mul(a, a, a);
            t >>= 1;
        }
    
        //输出结果
        printf("%d\n", g[1][(n - 1) * 9 + 1]);
        return 0;
    }
    
  • 相关阅读:
    龙威零式_团队项目例会记录_12
    龙威零式_团队项目例会记录_11
    龙威零式_团队项目例会记录_10
    个人项目--词频统计
    四人小组-----车辆管理系统+员工管理
    效能分析
    结对编程体会
    累计进度条 PSP 饼图
    词频统计更新
    四人项目设计
  • 原文地址:https://www.cnblogs.com/littlehb/p/16850595.html
Copyright © 2020-2023  润新知