刷矩乘的时候碰到这个题,但是图论题做得很少,所以就做自闭了qwq
既然这样那必须要写一篇题解啊qwq
设
f
t
,
i
,
j
\large f_{t,i,j}
ft,i,j 表示时间为
t
t
t 、起点为
i
i
i 时,刚好走到
j
j
j 点的路径数。
不难发现时间
t
=
1
t=1
t=1 时,
f
1
f_1
f1 这个矩阵就是读入数据中的矩阵,且答案就是
f
T
,
0
,
n
−
1
\large f_{T,0,n-1}
fT,0,n−1 。
我原来也想到了这一步,然后就开始大力推式子,不久后发现这道题远没有我想的那么简单(读者可自行尝试),因为最终推出来的式子没办法用矩阵乘法优化。
所以我们只能先从简单的情况下手:
如果
边权
∈
{
0
,
1
}
\text{边权} \in \{0,1\}
边权∈{0,1} ,这就相当于一个无权图,两点间仅有
相连
\text{相连}
相连 和
不相连
\text{不相连}
不相连 两种情况。
显然可以得到
f
t
,
i
,
j
=
∑
k
=
0
N
−
1
f
t
−
1
,
i
,
k
×
f
1
,
k
,
j
(
t
>
1
)
\large f_{t,i,j}=\sum\limits_{k=0}^{N-1}f_{t-1,i,k} \times f_{1,k,j}\quad (t>1)
ft,i,j=k=0∑N−1ft−1,i,k×f1,k,j(t>1)
这个式子和矩阵乘法的式子高度雷同,很容易看出
f
t
=
f
t
−
1
×
f
1
\large f_t=f_{t-1}\times f_1
ft=ft−1×f1
也就是
f
t
=
f
1
t
f_t={f_1}^t
ft=f1t 。然后直接矩阵快速幂就行了。
再回到原题的情况:
边权
∈
[
0
,
9
]
∩
Z
\text{边权} \in [\ 0,9\ ]\ \cap\ \Z
边权∈[ 0,9 ] ∩ Z
简单情况不是拿来骗分的,而是用来转化的。为了把边权转化为
0或1
\text{0或1}
0或1 ,我们需要拆点。
为了在拆点后的新图中还原原图,并且只用边权为 0或1 的边,我们需要将每个点都拆成 9 个点。这里我们用有序数对
(
i
,
j
)
(i,j)
(i,j) 表示原来的
i
i
i 点所拆出的第
j
j
j 个点。其中
i
∈
[
0
,
n
−
1
)
∩
Z
,
j
∈
[
0
,
9
)
∩
Z
i \in [0,n-1)\ \cap\ \Z \ ,\ j \in[0,9)\ \cap \ \Z
i∈[0,n−1) ∩ Z , j∈[0,9) ∩ Z 。钦定
(
i
,
0
)
(i,0)
(i,0) 为原来的
i
i
i 点。
我们需要这样连边:
(
i
,
0
)
←
(
i
,
1
)
←
⋯
←
(
i
,
7
)
←
(
i
,
8
)
(i,0)\leftarrow(i,1)\leftarrow\cdots\leftarrow(i,7)\leftarrow(i,8)
(i,0)←(i,1)←⋯←(i,7)←(i,8) 。
对于原图中
i
→
j
i\rightarrow j
i→j 权值为
v
v
v 的边,我们就只需要连上
(
i
,
0
)
→
(
j
,
v
−
1
)
(i,0)\rightarrow (j,v-1)
(i,0)→(j,v−1) 这条边即可。
设
(
i
,
j
)
(i,j)
(i,j) 对应的点编号为
i
+
j
×
n
i+j\times n
i+j×n ,新图就算是完成了。
原矩阵变为
9
n
×
9
n
9n\times 9n
9n×9n 的新矩阵,显然它也满足
f
t
=
f
1
t
f_t={f_1}^t
ft=f1t ,答案为
f
T
,
0
,
n
−
1
f_{T,0,n-1}
fT,0,n−1 。
时间复杂度:
O
(
(
9
n
)
3
log
T
)
=
O
(
729
n
3
log
T
)
\mathcal O(\ (9n)^3\log T)=\mathcal O(729n^3\log T)
O( (9n)3logT)=O(729n3logT)
奥义 · 大常数
反正能跑过去就行啦~
c o d e \large \rm code code
这是我目前跑得最快的一个版本,总用时 719 m s \rm 719ms 719ms 。
#pragma GCC optimize("Ofast") //卡常数专用,可忽略
#include<cstdio>
#include<cstring>
const int mod=2009;
int n,M,T;
struct node { int c[90][90]; }f;
void operator *= (node &a,node b) { //矩阵乘法
node s=a; memset(a.c,0,sizeof(a.c));
for (int i=0; i<M; ++i)
for (int j=0; j<M; ++j)
for (register int k=0; k<M; ++k)
a.c[i][j]=(a.c[i][j]+s.c[i][k]*b.c[k][j])%mod;
return ;
}
void operator ^= (node &a,int y) { //矩阵乘法
node ans=a; memset(a.c,0,sizeof(a.c));
for (register int i=0; i<M; ++i) a.c[i][i]=1;
while (y) {
if (y&1) a*=ans;
ans*=ans; y>>=1;
}
return ;
}
int main() {
scanf("%d%d",&n,&T); getchar(); //过滤回车
M=(n<<3)+n; register int s=0; //小优化,加比乘快
for (int i=0; i<n; ++i,s=i)
for (int j=0; j<8; ++j,s+=n) f.c[s+n][s]=1;
for (int i=0; i<n; ++i,getchar()) //getchar过滤回车
for (int j=0; j<n; ++j) {
int v=getchar()^48;
if (v) f.c[i][j+(v-1)*n]=1;
}
f^=T;
printf("%d",f.c[0][n-1]);
return 0;
}