題目的難度顏色使用 Luogu 上的分級,由簡單到困難分別為 🔴🟠🟡🟢🔵🟣⚫。

🔗 🟢 AT_dp_r Walk

Problem Statement

題目簡述

給定一個 NN 個頂點的有向圖(以鄰接矩陣 AA 給出,ai,j=1a_{i,j}=1 代表有邊,00 代表無邊),求圖中長度恰好為 KK 的有向路徑總數。答案對 109+710^9+7 取模。

Constraints

約束條件

  • 1N501 \le N \le 50
  • 1K10181 \le K \le 10^{18}
  • ai,j{0,1}a_{i,j} \in \{0, 1\},且 ai,i=0a_{i,i} = 0(無自環)

思路:矩陣快速冪

狀態定義與轉移

f[k][i][j]f[k][i][j] 為從頂點 ii恰好 kk到達頂點 jj 的路徑數。

根據「最後一步」的思想,列出轉移方程:

f[k][i][j]=m=1Nf[k1][i][m]×am,jf[k][i][j] = \sum_{m=1}^{N} f[k-1][i][m] \times a_{m,j}

直觀理解

要從 iikk 步到 jj,可以先走 k1k-1 步到任意中繼點 mm,再從 mm 經一條邊到 jj

邊界條件:f[0][i][j]=[i=j]f[0][i][j] = [i = j](走 0 步只能停在原地)。

矩陣乘法的本質

觀察轉移方程,它與矩陣乘法的定義完全一致:

(C)ij=m(A)im×(B)mj(C)_{ij} = \sum_{m} (A)_{im} \times (B)_{mj}

若將 f[k]f[k] 視為矩陣 FkF_k,鄰接矩陣為 AA,則:Fk=Fk1×AF_k = F_{k-1} \times A

由於 F0=IF_0 = I(單位矩陣),歸納可得:

Fk=AkF_k = A^k

結論

(AK)i,j(A^K)_{i,j} 即為從 iijj 長度恰為 KK 的路徑數。

快速冪加速

KK 可達 101810^{18},逐次相乘不可行。使用快速冪技巧,將乘法次數降至 O(logK)O(\log K)

最終答案為 AKA^K 中所有元素之和:

Answer=i,j(AK)i,j(mod109+7)\text{Answer} = \sum_{i,j} (A^K)_{i,j} \pmod{10^9+7}

複雜度分析

  • 時間複雜度O(N3logK)\mathcal{O}(N^3 \log K)
  • 空間複雜度O(N2)\mathcal{O}(N^2)

Code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
MOD = int(1e9 + 7)

class Matrix:
def __init__(self, mat):
self.mat = mat

def __mul__(self, other):
assert len(self.mat[0]) == len(other.mat)
return Matrix([[sum(x * y for x, y in zip(row, col)) % MOD for col in zip(*other.mat)]
for row in self.mat])

def __pow__(self, k):
assert len(self.mat) == len(self.mat[0])
n = len(self.mat)
res = Matrix([[int(i == j) for j in range(n)] for i in range(n)])
base = self
while k > 0:
if k & 1:
res *= base
base *= base
k >>= 1
return res

def __getitem__(self, i):
return self.mat[i]

def solve():
N, K = map(int, input().split())
A = [list(map(int, input().split())) for _ in range(N)]

A = Matrix(A)
Ak = A ** K
print(sum(sum(row, ) for row in Ak.mat))
ans = 0
for i in range(N):
for j in range(N):
ans = (ans + Ak[i][j]) % MOD
print(ans)

if __name__ == '__main__':
solve()