洛谷4159 [SCOI2009] 迷路(矩阵快速幂,拆点)
题意:该有向图有 n 个节点,节点从 1至 n 编号,windy 从节点 1 出发,他必须恰好在 t 时刻到达节点 n。现在给出该有向图,你能告诉 windy 总共有多少种不同的路径吗?答案对2009取模。(洛谷 4159)
输入格式:第一行包含两个整数,分别代表 n和 t。第 2 到第 (n + 1)行,每行一个长度为 n 的字符串,第 (i + 1)行的第 j 个字符c [ i ] [ j ]是一个数字字符,若为 0,则代表节点 i 到节点 j 无边,否则代表节点 i 到节点 j 的边的长度为c [ i ] [ j ]。
输出格式:输出一行一个整数代表答案对 2009取模的结果。
数据范围: 2 ≤ n ≤ 10 , 1 ≤ t ≤ 1e9。
分析:首先考虑边权只有 $0,1$ 的情况。令 $f_1=$给定矩阵。因为边权只有 $0,1$,所以我们可以将这个矩阵的意义转化成: $f_t[i][j]=k \Longleftrightarrow$ $i$到 $j$的长度为 $t$的路径条数为 $k$。显然有 $f_t[i][j]=\sum\limits_{k=1}^nf_{t-1}[i][k]\times f_1[k][j]$。
矩阵乘法满足结合律,故 $f_t=f_1^t$。于是这种情况下答案就是 $f_T[1][n]$。考虑边权 $w\in [0,9]\cap\mathbb Z$ 的情况。因为边权可能大于1,所以给定矩阵不能直接转换成那种含义了。但是我们发现 $n\le 10$。这意味着我们可以将每个点都拆开,将这张图转化成边权只有 $0,1$ 的图,这样上面的意义就成立了。
我们发现可以将每个点拆成 $9$ 个点,令有序数对 $(i,j)(i\in [1,n]\cap\mathbb Z,j\in [0,8]\cap\mathbb Z)$ 表示点 $i$ 拆成的第 $j$ 个点,其中第 $0$ 个点是“真”点,其余的是“假”点。我们可以令 $(i,j)(j\in [1,8]\cap\mathbb Z)$ 表示到“真”点 $(i,0)$ 的距离为 $j$ 的“假”点,只要让 $(i,j)(j\in [1,8]\cap\mathbb Z)$ 向 $(i,j-1)$ 连一条边权为 $1$ 的边。这样我们就还原了原图中的边,并且将边权都转化成了 $0,1$。而每个 $(i,j)$ 又可以唯一对应一个编号 $i+j\times n$,因此原矩阵就变成了一个 $9n\times 9n$ 的矩阵 $f_1$。根据前面的推理,同样 $f_t=f_1^t$。答案就是 $f_T[1][n]$。
#include<cstdio> #include<cstring> const int mod = 2009; int n,T,m,x; struct Node{ int a[100][100]; Node operator *(const Node &x)const{ Node ans; memset(ans.a,0,sizeof(ans.a)); for(int i = 1; i <= m; ++i) for(int t = 1; t <= m; ++t) for(int k = 1; k <= m; ++k) ans.a[i][t] = (ans.a[i][t]+a[i][k]*x.a[k][t]) % mod; return ans; } }res,ans; inline int cal(int x,int y){ return x+y*n; } void quick_pow(int k){ ans = res; while(k){ if(k&1) ans = ans*res; res = res*res; k >>= 1; } } int main(){ scanf("%d%d",&n,&T); m = 9*n; for(int i = 1; i <= n; ++i){ for(int t = 1; t <= 8; ++t) res.a[cal(i,t)][cal(i,t-1)] = 1; for(int t = 1; t <= n; ++t){ scanf("%1d",&x); if(x) res.a[i][cal(t,x-1)] = 1; } } quick_pow(T-1); printf("%d",ans.a[1][n]); return 0; }
原文链接:https://www.luogu.com.cn/blog/samxiang/solution-p4159