9.7 KiB
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
来表示两个点之间是否存在边,并且用邻接矩阵来存这张图,那么我们又可以得到些什么呢?
举个栗子
以邻接矩阵来表示这个矩阵:
$\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}$$
将其拆点:
<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;
}