You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

9.7 KiB

This file contains invisible Unicode characters!

This file contains invisible Unicode characters that may be processed differently from what appears below. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to reveal hidden characters.

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

P4159 [SCOI2009] 迷路

题目传送门

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

一、题目描述

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

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

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

答案对 2009 取模。

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

输入格式 第一行包含两个整数,分别代表 nt

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 的路径条数

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

举个栗子

我们以下面这张图为例 20221102111851

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

其中,a_{ij} 表示ij之间是否有连线;

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

$\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}表示由ij经过x条边的情况总数

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

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

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

显然是不可以的。

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

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

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

!(⊙ o ⊙)

怎么办呢?

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

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

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

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

但怎么拆点呢?

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

可得矩阵

\large \begin{bmatrix}
 0 & 2 \\ 
 2 & 1 
\end{bmatrix}$$

将其拆点:
<center><img src='https://img-blog.csdnimg.cn/d114eb837c744792bf579cbde9d99755.png' width=40%></center>


可得到新矩阵 
$$\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;
}