矩阵加速
前置芝士
- 一小部分的线性代数知识(矩阵部分)
- 快速幂
- 快速乘(
龟速乘,因为有时直接乘法会超 l o n g l o n g longlong longlong,所以改二进制加法防溢出,原理和快速幂相似)
矩阵乘法
[ 1 2 3 4 5 6 7 8 9 ] (1) \left[ \begin{matrix} 1&2&3\\ 4&5&6\\ 7&8&9\\ \end{matrix} \right] \tag{1} 147258369 (1)
如图,这是一个3*3的矩阵,矩阵在线性代数中有着重要地位,虽然单看有一点抽象,如果学习了线性代数就会发现它是一个很棒的工具,如果没有学过线性代数,没关系,这篇文章会用后面的例题带你了解它的玄妙之美
设 A n 1 ∗ n 2 A_{n_1*n_2} An1∗n2, B m 1 ∗ m 2 B_{m_1*m_2} Bm1∗m2, C k 1 ∗ k 2 C_{k_1*k_2} Ck1∗k2均为矩阵
矩阵A*B成立的充要条件: n 2 = = m 1 n_2==m_1 n2==m1
设 D = A B D=AB D=AB,矩阵中的每个元素 d i , j = ∑ k = 1 n 2 a i , k b k , j d_{i,j}=\sum_{k=1}^{n_2}{a_{i,k}b_{k,j}} di,j=∑k=1n2ai,kbk,j
矩阵乘法满足结合律,但不满足交换律!!!
于是我们就可以用 O ( n 3 ) O(n^3) O(n3)的时间复杂度直接模拟这个乘法过程
void mul(int a[55][55],int b[55][55]){
for (int i=0;i<n;i++){
for (int j=0;j<n;j++){
u[i][j]=0;
for (int z=0;z<n;z++){
u[i][j]+=a[i][z]*b[z][j];
u[i][j]%=mod;
}
}
}
for (int i=0;i<n;i++){
for (int j=0;j<n;j++){
a[i][j]=u[i][j];
}
}
}
因为它满足结合律,所以我们可以像快速幂那样,把他按照2的k次幂快速合并矩阵乘法求结果
于是有了板子题:P3390 【模板】矩阵快速幂
#include<bits/stdc++.h>
#define int long long
using namespace std;
int mod=1e9+7;
int n,k,ans[110][110],x[110][110],u[110][110];
signed main(){
scanf("%lld%lld",&n,&k);
for (int i=1;i<=n;i++){
for (int j=1;j<=n;j++){
scanf("%lld",&x[i][j]);
}
ans[i][i]=1ll;
}
while (k){
if (k&1){
for (int i=1;i<=n;i++){
for (int j=1;j<=n;j++){
u[i][j]=0ll;
for (int z=1;z<=n;z++){
u[i][j]+=ans[i][z]*x[z][j]%mod;
u[i][j]%=mod;
}
}
}
for (int i=1;i<=n;i++){
for (int j=1;j<=n;j++){
ans[i][j]=u[i][j];
}
}
}
k>>=1;
for (int i=1;i<=n;i++){
for (int j=1;j<=n;j++){
u[i][j]=0ll;
for (int z=1;z<=n;z++){
u[i][j]+=x[i][z]*x[z][j]%mod;
u[i][j]%=mod;
}
}
}
for (int i=1;i<=n;i++){
for (int j=1;j<=n;j++){
x[i][j]=u[i][j];
}
}
}
for (int i=1;i<=n;i++){
for (int j=1;j<=n;j++){
printf("%lld ",ans[i][j]);
}
printf("\n");
}
return 0;
}
矩阵加速进阶应用
- 加速数列
- 加速DP转移
- 加速求路径方案
加速数列:求斐波那契数列(典)
大家都知道,斐波那契数列是满足如下性质的一个数列:
F
n
=
{
1
(
n
≤
2
)
F
n
−
1
+
F
n
−
2
(
3
≤
n
)
问题范围:
1
≤
n
≤
2
63
F_n=\begin{cases} 1 & (n \leq 2)\\ F_{n-1}+F_{n-2}& (3 \leq n) \end{cases} \\ 问题范围:1\leq n\leq2^{63}
Fn={1Fn−1+Fn−2(n≤2)(3≤n)问题范围:1≤n≤263
这时,我们怎么把数列公式和矩阵乘法结合起来?
答案是:
[
F
n
F
n
−
1
]
=
[
F
n
−
1
F
n
−
2
]
[
1
0
1
1
]
(2)
\left[ \begin{matrix} F_n&F_{n-1} \end{matrix} \right] \tag{2}= \left[ \begin{matrix} F_{n-1}&F_{n-2} \end{matrix} \right] \left[ \begin{matrix} 1&0\\ 1&1 \end{matrix} \right]
[FnFn−1]=[Fn−1Fn−2][1101](2)
因为每一项新的项于前两项相关,有点像高中求等比数列那样子,配凑出这个样子出来
[
F
n
F
n
−
1
]
=
[
F
2
F
1
]
[
1
0
1
1
]
n
−
2
(
F
2
=
1
,
F
1
=
1
)
(3)
\left[ \begin{matrix} F_n&F_{n-1} \end{matrix} \right] \tag{3}= \left[ \begin{matrix} F_{2}&F_{1} \end{matrix} \right] \left[ \begin{matrix} 1&0\\ 1&1 \end{matrix} \right]^{n-2} (F_2=1,F_1=1)
[FnFn−1]=[F2F1][1101]n−2(F2=1,F1=1)(3)
还有几道相似的例题:
广义的斐波那契数列是指形如 a n = p × a n − 1 + q × a n − 2 a_n=p×a_{n−1}+q×a_{n−2} an=p×an−1+q×an−2 的数列。
今给定数列的两系数 p 和 q,以及数列的最前两项 a 1 a_1 a1 和 a 2 a_2 a2,另给出两个整数 n 和 m,试求数列的第 n 项 a n a_n an 对 m 取模后的结果。
[ A n A n − 1 ] = [ A n − 1 A 2 ] [ p 0 q 1 ] \left[ \begin{matrix} A_n & A_{n-1} \end{matrix} \right]= \left[ \begin{matrix} A_{n-1}&A_2 \end{matrix} \right] \left[ \begin{matrix} p&0\\q&1 \end{matrix} \right] [AnAn−1]=[An−1A2][pq01]
a n = { 1 ( n ≤ 3 ) a n − 1 + a n − 3 ( 4 ≤ n ) a_n=\begin{cases} 1 & (n \leq 3)\\ a_{n-1}+a_{n-3}& (4 \leq n) \end{cases} \\ an={1an−1+an−3(n≤3)(4≤n)
[ a n a n − 1 a n − 2 ] = [ a n − 1 a n − 2 a n − 3 ] ∗ [ 1 1 0 0 0 1 1 0 0 ] \left[ \begin{matrix} a_n&a_{n-1}&a_{n-2} \end{matrix} \right]= \left[ \begin{matrix} a_{n-1}&a_{n-2}&a_{n-3} \end{matrix} \right] * \left[ \begin{matrix} 1&1&0\\ 0&0&1\\ 1&0&0\\ \end{matrix} \right] [anan−1an−2]=[an−1an−2an−3]∗ 101100010
X n + 1 = ( a ∗ X n + c ) m o d m X_{n+1}=(a*X_n+c)modm Xn+1=(a∗Xn+c)modm
[ X n c ] = [ X n − 1 c ] ∗ [ a 0 1 1 ] \left[ \begin{matrix} X_n&c \end{matrix} \right]= \left[ \begin{matrix} X_{n-1}&c \end{matrix} \right] * \left[ \begin{matrix} a&0\\ 1& 1\\ \end{matrix} \right] [Xnc]=[Xn−1c]∗[a101]
优化DP转移
矩阵加速常常作为一个优化技巧在DP中转移
P4838 P哥破解密码(DP+矩阵加速)
定义一个串合法,当且仅当串只由 A 和 B 构成,且没有连续的 3 个 A。P 哥知道,密码就是长度为 N的合法字符串数量对
19260817 取模的结果。但是 P 哥不会算,所以他只能把 N 告诉你,让你来算。
至于为什么要对这个数取模,好像是因为纪念某个人,但到底是谁,P 哥也不记得了。
然而他忘记字符串长度 N应该是多少了,于是他准备试 M 组数据。
N ≤ 1 0 9 N\leq 10^9 N≤109
其实不难想到这是一个DP:设
f
[
i
]
[
j
]
f[i][j]
f[i][j]为长度为
i
i
i结尾有就
j
j
j个A的方案数
f
[
i
]
[
0
]
=
∑
k
=
0
2
f
[
i
−
1
]
[
k
]
f
[
i
]
[
1
]
=
f
[
i
−
1
]
[
0
]
f
[
i
]
[
2
]
=
f
[
i
−
1
]
[
1
]
f[i][0]=\sum_{k=0}^2 f[i-1][k]\\ f[i][1]=f[i-1][0]\\ f[i][2]=f[i-1][1]
f[i][0]=k=0∑2f[i−1][k]f[i][1]=f[i−1][0]f[i][2]=f[i−1][1]
但是这个时间复杂度是
O
(
N
)
O(N)
O(N),很明显会超时!这时我们发现它好像可以通过矩阵来转移
[
f
[
i
]
[
0
]
f
[
i
]
[
1
]
f
[
i
]
[
2
]
]
=
[
f
[
i
−
1
]
[
0
]
f
[
i
−
1
]
[
1
]
f
[
i
−
1
]
[
2
]
]
∗
[
1
1
0
1
0
1
1
0
0
]
\left[ \begin{matrix} f[i][0]&f[i][1]&f[i][2] \end{matrix} \right]= \left[ \begin{matrix} f[i-1][0]&f[i-1][1]&f[i-1][2] \end{matrix} \right] * \left[ \begin{matrix} 1&1&0\\ 1&0&1\\ 1&0&0\\ \end{matrix} \right]
[f[i][0]f[i][1]f[i][2]]=[f[i−1][0]f[i−1][1]f[i−1][2]]∗
111100010
于是我们就通过矩阵快速幂来加速
P3193 HNOI2008 GT考试(DP+矩阵加速+KMP)
阿申准备报名参加 GT 考试,准考证号为 N N N 位数 X 1 , X 2 … X n ( 0 ≤ X i ≤ 9 ) X_1,X_2…X_n\ (0\le X_i\le 9) X1,X2…Xn (0≤Xi≤9),他不希望准考证号上出现不吉利的数字。
他的不吉利数字 A 1 , A 2 , ⋯ , A m ( 0 ≤ A i ≤ 9 ) A_1,A_2,\cdots, A_m\ (0\le A_i\le 9) A1,A2,⋯,Am (0≤Ai≤9) 有 M M M 位,不出现是指 X 1 , X 2 ⋯ X n X_1,X_2\cdots X_n X1,X2⋯Xn 中没有恰好一段等于 A 1 , A 2 , ⋯ , A m A_1,A_2,\cdots ,A_m A1,A2,⋯,Am, A 1 A_1 A1 和 X 1 X_1 X1 可以为 0 0 0。对于全部数据, N ≤ 1 0 9 N\leq10^9 N≤109, M ≤ 20 M\leq 20 M≤20, K ≤ 1000 K\leq1000 K≤1000。
设 f [ i ] [ j ] f[i][j] f[i][j]表示准考证前 i i i位中第i位恰好匹配到第 j j j个不吉利数字的方案数。
如果我们在第 i + 1 i+1 i+1位加了字符x(会发现有三种转移情况):
- f [ i + 1 ] [ j + 1 ] + = f [ i ] [ j ] ( x = = A j + 1 ) f[i+1][j+1]+=f[i][j](x==A_{j+1}) f[i+1][j+1]+=f[i][j](x==Aj+1)
- f [ i + 1 ] [ 0 ] + = f [ i ] [ j ] ( x ! = A j + 1 而且完全失配 ) f[i+1][0]+=f[i][j](x!=A_{j+1}而且完全失配) f[i+1][0]+=f[i][j](x!=Aj+1而且完全失配)
- f [ i + 1 ] [ k ] + = f [ i ] [ j ] ( x ! = A j + 1 但是是部分失配,然后因为一部分前缀和后缀相同 ) f[i+1][k]+=f[i][j](x!=A_{j+1}但是是部分失配,然后因为一部分前缀和后缀相同) f[i+1][k]+=f[i][j](x!=Aj+1但是是部分失配,然后因为一部分前缀和后缀相同)
发现其实这就是
K
M
P
KMP
KMP!,对于每次我们其实就是枚举加哪个字符然后跳border找最大匹配得上的border,其实这里我们可以预处理
它每一层的转移都相同(
从
i
到
i
+
1
从i到i+1
从i到i+1),所以这就和上题有异曲同工之妙了,每层利用矩阵转移,初始矩阵只有
f
[
0
]
[
0
]
=
1
f[0][0]=1
f[0][0]=1,其余均为零
#include<bits/stdc++.h>
using namespace std;
int mod;
int ans[22][22],x[22][22],u[22][22],nxt[50],n,m,k,ansh;
string a;
void mul(int a[22][22],int b[22][22]){
for (int i=0;i<m;i++){
for (int j=0;j<m;j++){
for (int z=0;z<m;z++){
u[i][j]+=a[i][z]*b[z][j];
u[i][j]%=mod;
}
}
}
for (int i=0;i<m;i++){
for (int j=0;j<m;j++){
a[i][j]=u[i][j];
u[i][j]=0;
}
}
}
int main(){
scanf("%d%d%d",&n,&m,&mod);
cin>>a;
nxt[0]=0;
nxt[1]=0;
for (int i=2;i<=m;i++){//KMP
nxt[i]=nxt[i-1];
while (nxt[i]&&a[nxt[i]]!=a[i-1])nxt[i]=nxt[nxt[i]];
nxt[i]+=(a[nxt[i]]==a[i-1]);
}
ans[0][0]=1;
for (int i=0;i<m;i++){//这里就是每一层的匹配位数枚举
for (int j=0;j<=9;j++){//枚举新加的字符
k=i;
while (k&&a[k]!=(char)('0'+j))k=nxt[k];
k+=(a[k]==(char)('0'+j));
x[i][k]++;//说明存在一种方案从使得匹配位置从i到k
}
}
while (n){
if (n&1){
mul(ans,x);
}
n>>=1;
mul(x,x);
}
ansh=0;
for (int i=0;i<m;i++)ansh=(ansh+ans[0][i])%mod;
printf("%d",ansh);
return 0;
}
这里还缺了一个AC自动机+DP+矩阵快速幂(待)
加速求路径方案
提起矩阵,联系图论,我们不然而然地联想到了邻接矩阵
如果我们把邻接矩阵求k次幂,那么我们得到了矩阵A,
a
i
,
j
a_{i,j}
ai,j表示从
i
i
i出发到
j
j
j,每次走一条边,恰好走K条边的方案数
但是,一般考路径矩阵加速都很考验建图技巧
加里敦星球的人们特别喜欢喝可乐。因而,他们的敌对星球研发出了一个可乐机器人,并且放在了加里敦星球的 1 1 1 号城市上。这个可乐机器人有三种行为: 停在原地,去下一个相邻的城市,自爆。它每一秒都会随机触发一种行为。现在给加里敦星球城市图,在第 0 0 0 秒时可乐机器人在 1 1 1 号城市,问经过了 t t t 秒,可乐机器人的行为方案数是多少?
第一行输入两个正整数 N , M N,M N,M , N N N 表示城市个数, M M M 表示道路个数。
接下来 M M M 行输入 u , v u,v u,v ,表示 u , v u,v u,v 之间有一条双向道路。
最后输入时间 t t t
对于 100 % 100\% 100% 的数据, n , m ≤ 100 n,m\leq 100 n,m≤100 , $ t\leq 10^9$ 。
我们按照给出的边可以得出该题的邻接矩阵,但是本题难点是自爆该怎么处理?因为自爆之后就结束,因为这个自爆的操作没法用邻接矩阵表示,所有我们目前没法用矩阵快速幂
但是,其实根据自爆的特性(可以在任何时刻,且自爆之后就没法继续了),我们虚构了一个超级点:每一个城市都可以到达,但是到达之后没法离开(也就是自环) (虚构条件点)
于是我们可以用邻接矩阵来表示自爆
#include<bits/stdc++.h>
#define int long long
using namespace std;
int mod=2017;
int n,m,uu,vv,edge[150][150],ans[150][150],u[150][150],t,ansh;
void mul(int a[150][150],int b[150][150]){
for (int i=0;i<=n;i++){
for (int j=0;j<=n;j++){
u[i][j]=0ll;
for (int z=0;z<=n;z++){
u[i][j]+=a[i][z]*b[z][j];
u[i][j]%=mod;
}
}
}
for (int i=0;i<=n;i++){
for (int j=0;j<=n;j++){
a[i][j]=u[i][j];
}
}
return ;
}
signed main(){
scanf("%lld%lld",&n,&m);
for (int i=1;i<=m;i++){
scanf("%lld%lld",&uu,&vv);
edge[uu][vv]=1;
edge[vv][uu]=1;
}
//0点就是我所说的“超级点”
edge[0][0]=1;
for (int i=1;i<=n;i++){
edge[i][0]=1;
edge[i][i]=1;
ans[i][i]=1;
}
scanf("%lld",&t);
while (t){
if (t&1)mul(ans,edge);
t>>=1;
mul(edge,edge);
}
for (int i=0;i<=n;i++)ansh=(ansh+ans[1][i])%mod;//求一号点出发的所有方案数
printf("%lld",ansh);
return 0;
}
潘塔纳尔沼泽地号称世界上最大的一块湿地,它地位于巴西中部马托格罗索州的南部地区。每当雨季来临,这里碧波荡漾、生机盎然,引来不少游客。
为了让游玩更有情趣,人们在池塘的中央建设了几座石墩和石桥,每座石桥连接着两座石墩,且每两座石墩之间至多只有一座石桥。这个景点造好之后一直没敢对外开放,原因是池塘里有不少危险的食人鱼。
豆豆先生酷爱冒险,他一听说这个消息,立马赶到了池塘,想做第一个在桥上旅游的人。虽说豆豆爱冒险,但也不敢拿自己的性命开玩笑,于是他开始了仔细的实地勘察,并得到了一些惊人的结论:食人鱼的行进路线有周期性,这个周期只可能是 2 2 2、 3 3 3 或者 4 4 4 个单位时间。每个单位时间里,食人鱼可以从一个石墩游到另一个石墩。每到一个石墩,如果上面有人它就会实施攻击,否则继续它的周期运动。如果没有到石墩,它是不会攻击人的。
借助先进的仪器,豆豆很快就摸清了所有食人鱼的运动规律,他要开始设计自己的行动路线了。每个单位时间里,他只可以沿着石桥从一个石墩走到另一个石墩,而不可以停在某座石墩上不动,因为站着不动还会有其它危险。如果豆豆和某条食人鱼在同一时刻到达了某座石墩,就会遭到食人鱼的袭击,他当然不希望发生这样的事情。
现在豆豆已经选好了两座石墩 S t a r t \mathrm{Start} Start 和 E n d \mathrm{End} End,他想从 S t a r t \mathrm{Start} Start 出发,经过 K K K 个单位时间后恰好站在石墩 E n d \mathrm{End} End 上。假设石墩可以重复经过(包括 S t a r t \mathrm{Start} Start 和 E n d \mathrm{End} End),他想请你帮忙算算,这样的路线共有多少种(当然不能遭到食人鱼的攻击)。
输入格式
输入文件共 M + 2 + N F i s h M + 2 + \mathrm{NFish} M+2+NFish 行。
第一行包含五个正整数 N , M , S t a r t , E n d , K N,M,\mathrm{Start},\mathrm{End},K N,M,Start,End,K,分别表示石墩数目、石桥数目、 S t a r t \mathrm{Start} Start 石墩和 E n d \mathrm{End} End 石墩的编号和一条路线所需的单位时间。石墩用 0 0 0 到 N − 1 N-1 N−1 的整数编号。
第 2 2 2 到 M + 1 M + 1 M+1 行,给出石桥的相关信息。每行两个整数 x x x 和 y y y, 0 ≤ x , y ≤ N − 1 0 \leq x, y \leq N-1 0≤x,y≤N−1,表示这座石桥连接着编号为 x x x 和 y y y 的两座石墩。
第 M + 2 M + 2 M+2 行是一个整数 N F i s h \mathrm{NFish} NFish,表示食人鱼的数目。
第 M + 3 M + 3 M+3 到 M + 2 + N F i s h M + 2 + \mathrm{NFish} M+2+NFish 行,每行给出一条食人鱼的相关信息。每行的第一个整数是 T T T, T = 2 , 3 T = 2,3 T=2,3 或 4 4 4,表示食人鱼的运动周期。接下来有 T T T 个数,表示一个周期内食人鱼的行进路线。
如果 T = 2 T=2 T=2,接下来有 2 2 2 个数 P 0 P_0 P0 和 P 1 P_1 P1,食人鱼从 P 0 P_0 P0 到 P 1 P_1 P1,从 P 1 P_1 P1 到 P 0 , … P_0,\ldots P0,…;
如果 T = 3 T=3 T=3,接下来有 3 3 3 个数 P 0 , P 1 P_0,P_1 P0,P1 和 P 2 P_2 P2,食人鱼从 P 0 P_0 P0 到 P 1 P_1 P1,从 P 1 P_1 P1 到 P 2 P_2 P2,从 P 2 P_2 P2 到 P 0 , … P_0,\ldots P0,…;
如果 T = 4 T=4 T=4,接下来有 4 4 4 个数 P 0 , P 1 , P 2 P_0,P_1,P_2 P0,P1,P2 和 P 3 P_3 P3,食人鱼从 P 0 P_0 P0 到 P 1 P_1 P1,从 P 1 P_1 P1 到 P 2 P_2 P2,从 P 2 P_2 P2 到 P 3 P_3 P3,从 P 3 P_3 P3 到 P 0 , … P_0,\ldots P0,…。
豆豆出发的时候所有食人鱼都在自己路线上的 P 0 P_0 P0 位置,请放心,这个位置不会是 S t a r t \mathrm{Start} Start 石墩。
对于 100 % 100 \% 100% 的数据, 1 ≤ N ≤ 50 1 \leq N \leq 50 1≤N≤50, 1 ≤ K ≤ 2 × 1 0 9 1 \leq K \leq 2 \times 10^9 1≤K≤2×109, 1 ≤ N F i s h ≤ 20 1 \leq \mathrm{NFish} \leq 20 1≤NFish≤20。
当我们写多了邻接矩阵题,会发现邻接矩阵可以加速的条件是每个时刻的边都相同(即邻接矩阵相同),但是本题因为有食人鱼所以有时候有些边走不了,**矩阵会随时间变化!**怎么办?
看了一下,T只有三种情况:2、3、4,而且是==呈现周期规律==,这时我们可以得出12为2、3、4的公倍数,每相邻的十二次操作答案都是一样的,所以我们可以先预处理前十二次操作,设 K = 12 ∗ q + p ( p ≤ 11 ) K=12*q+p(p\leq11) K=12∗q+p(p≤11),前面 12 ∗ q 12*q 12∗q次操作可以加速,然后再单独乘上前面 p p p个时刻
#include<bits/stdc++.h>
#define int long long
using namespace std;
int mod=10000;
int uu,vv,nowx,p[55],v[55][5],Fish,h,yu;
int n,m,s,e,k,edge[55][55],now[55][55],f[15][55][55],u[55][55],ans[55][55];
void mul(int a[55][55],int b[55][55]){
for (int i=0;i<n;i++){
for (int j=0;j<n;j++){
u[i][j]=0;
for (int z=0;z<n;z++){
u[i][j]+=a[i][z]*b[z][j];
u[i][j]%=mod;
}
}
}
for (int i=0;i<n;i++){
for (int j=0;j<n;j++){
a[i][j]=u[i][j];
}
}
}
signed main(){
scanf("%lld%lld%lld%lld%lld",&n,&m,&s,&e,&k);
for (int i=0;i<n;i++)f[0][i][i]=now[i][i]=1,ans[i][i]=1;
for (int i=1;i<=m;i++){
scanf("%lld%lld",&uu,&vv);
edge[uu][vv]=1;
edge[vv][uu]=1;
}
scanf("%lld",&Fish);
for (int i=1;i<=Fish;i++){
scanf("%lld",&p[i]);
for (int j=0;j<p[i];j++)scanf("%lld",&v[i][j]);
}
for (int k=1;k<=12;k++){
mul(now,edge);
for (int i=1;i<=Fish;i++){
nowx=v[i][k%p[i]];
for (int j=0;j<n;j++)now[j][nowx]=0;
}
for (int i=0;i<n;i++){
for (int j=0;j<n;j++){
f[k][i][j]=now[i][j];
}
}
}
h=k/12;
yu=k%12;
while (h){
if (h&1)mul(ans,now);
h>>=1;
mul(now,now);
}
mul(ans,f[yu]);
printf("%lld",ans[s][e]);
return 0;
}
该有向图有 n n n 个节点,节点从 1 1 1 至 n n n 编号,windy 从节点 1 1 1 出发,他必须恰好在 t t t 时刻到达节点 n n n。
现在给出该有向图,你能告诉 windy 总共有多少种不同的路径吗?
答案对 2009 2009 2009 取模。
注意:windy 不能在某个节点逗留,且通过某有向边的时间严格为给定的时间。
- 对于 100 % 100\% 100% 的数据,保证 2 ≤ n ≤ 10 2 \leq n \leq 10 2≤n≤10, 1 ≤ t ≤ 1 0 9 1 \leq t \leq 10^9 1≤t≤109。
这题看上去有点板子,但是我们发现它和我们板子的每条边用时都为1不同,这题边的时间是 1 − 9 1-9 1−9,这时就用到
套路——拆点:把每个点x拆成九个假点(分别表示距离目的地还有 0 − 8 0-8 0−8次操作),我们可以给它编号 x ∗ 9 + i x*9+i x∗9+i
- 每次操作,每个假点都会向前一个假点连边 a d d ( x ∗ 9 + i , x ∗ 9 + i − 1 ) add(x*9+i,x*9+i-1) add(x∗9+i,x∗9+i−1)
- 每一条边 u − v ( 用时 t ) u-v(用时t) u−v(用时t),就是建边 a d d ( u ∗ 9 + 0 , v ∗ 9 + t − 1 ) add(u*9+0,v*9+t-1) add(u∗9+0,v∗9+t−1)
#include<bits/stdc++.h>
#define int long long
using namespace std;
int u[110][110],edge[110][110],ans[110][110];
int n,mod=2009,T;
char g;
int get_num(int x,int y){
return (x-1)*9+y;
}
void mul(int a[110][110],int b[110][110]){
for (int i=0;i<9*n;i++){
for (int j=0;j<9*n;j++){
u[i][j]=0;
for (int z=0;z<9*n;z++){
u[i][j]+=a[i][z]*b[z][j];
u[i][j]%=mod;
}
}
}
for (int i=0;i<9*n;i++){
for (int j=0;j<9*n;j++){
a[i][j]=u[i][j];
}
}
}
void print(int x[110][110]){
for (int i=0;i<9*n;i++){
for (int j=0;j<9*n;j++){
printf("%lld ",x[i][j]);
}
printf("\n");
}
printf("!!!!!!\n");
return ;
}
signed main(){
scanf("%lld%lld",&n,&T);
for (int i=0;i<9*n;i++){
ans[i][i]=1;
}
for (int i=1;i<=n;i++){
for (int j=1;j<9;j++)edge[get_num(i,j)][get_num(i,j-1)]=1;
for (int j=1;j<=n;j++){
cin>>g;
if (g!='0')edge[get_num(i,0)][get_num(j,g-'0'-1)]=1;
}
}
while (T){
if (T&1){
mul(ans,edge);
}
T>>=1;
mul(edge,edge);
}
printf("%lld",ans[get_num(1,0)][get_num(n,0)]);
return 0;
}
[SDOI2009] HH去散步
题目描述
HH 有个一成不变的习惯,喜欢饭后百步走。所谓百步走,就是散步,就是在一定的时间内,走过一定的距离。但是同时 HH 又是个喜欢变化的人,所以他不会立刻沿着刚刚走来的路走回。又因为 HH 是个喜欢变化的人,所以他每天走过的路径都不完全一样,他想知道他究竟有多少种散步的方法。
现在给你学校的地图(假设每条路的长度都是一样的都是 1 1 1),问长度为 t t t,从给定地点 A A A 走到给定地点 B B B 共有多少条符合条件的路径。
对于 100 % 100\% 100% 的数据, N ≤ 50 N \le 50 N≤50, M ≤ 60 M \le 60 M≤60, t ≤ 2 30 t \le 2^{30} t≤230, 0 ≤ A , B 0 \le A,B 0≤A,B。
这题难点就是它有一个限制:一条边不能连续地走两次
我们邻接矩阵存不了这个信息,邻接矩阵的只有点的信息。
把边化点(这个思路很妙):我们正常的邻接矩阵存的是点到点的方案,这时我们用全新的邻接矩阵存的是边到边的方案(这时边就成了有向边,所以一条边不能连续地走两次变成一个边不能走它的反向边)
#include<bits/stdc++.h>
using namespace std;
int mod=45989;
int n,m,t,A,B,ans[150][150],x[150][150],u[150][150],ansh,cnt,uu,vv;
vector<int> hd[150];
int edge[150];
void mul(int a[150][150],int b[150][150]){
for (int i=1;i<=cnt;i++){
for (int j=1;j<=cnt;j++){
for (int z=1;z<=cnt;z++){
u[i][j]+=a[i][z]*b[z][j]%mod;
u[i][j]%=mod;
}
}
}
for (int i=1;i<=cnt;i++){
for (int j=1;j<=cnt;j++){
a[i][j]=u[i][j];
u[i][j]=0;
}
}
return ;
}
int k(int a){//反向边就是这相邻的2k+1和2K+2
if (a%2==0)return a-1;
else return a+1;
}
int main(){
scanf("%d%d%d%d%d",&n,&m,&t,&A,&B);
for (int i=1;i<=m;i++){
scanf("%d%d",&uu,&vv);
edge[++cnt]=vv;
hd[uu].push_back(cnt);
edge[++cnt]=uu;
hd[vv].push_back(cnt);
}
for (int i=1;i<=cnt;i++){
for (int j=0;j<hd[edge[i]].size();j++){
if (k(i)==hd[edge[i]][j])continue;
x[i][hd[edge[i]][j]]=1;
}
}
if (t==0){
printf("0\n");
return 0;
}
t--;//这个第一步处理,不然不好初始化
for (int i=0;i<hd[A].size();i++){
ans[hd[A][i]][hd[A][i]]=1;
}
while (t){
if (t&1){
mul(ans,x);
}
t>>=1;
mul(x,x);
}
for (int i=0;i<hd[A].size();i++){
for (int j=1;j<=cnt;j++){
if (edge[j]==B){
ansh=(ansh+ans[hd[A][i]][j])%mod;
}
}
}
printf("%d",ansh);
return 0;
}