矩阵加速

矩阵加速

前置芝士

  • 一小部分的线性代数知识(矩阵部分)
  • 快速幂
  • 快速乘龟速乘,因为有时直接乘法会超 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} An1n2 B m 1 ∗ m 2 B_{m_1*m_2} Bm1m2 C k 1 ∗ k 2 C_{k_1*k_2} Ck1k2均为矩阵
矩阵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={1Fn1+Fn2(n2)(3n)问题范围:1n263

这时,我们怎么把数列公式和矩阵乘法结合起来?

答案是:
[ 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] [FnFn1]=[Fn1Fn2][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) [FnFn1]=[F2F1][1101]n2(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×an1+q×an2 的数列。

今给定数列的两系数 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] [AnAn1]=[An1A2][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={1an1+an3(n3)(4n)

[ 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] [anan1an2]=[an1an2an3] 101100010

X n + 1 = ( a ∗ X n + c ) m o d m X_{n+1}=(a*X_n+c)modm Xn+1=(aXn+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]=[Xn1c][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 N109

其实不难想到这是一个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=02f[i1][k]f[i][1]=f[i1][0]f[i][2]=f[i1][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[i1][0]f[i1][1]f[i1][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,X2Xn (0Xi9),他不希望准考证号上出现不吉利的数字。
他的不吉利数字 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 (0Ai9) M M M 位,不出现是指 X 1 , X 2 ⋯ X n X_1,X_2\cdots X_n X1,X2Xn 中没有恰好一段等于 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 N109 M ≤ 20 M\leq 20 M20 K ≤ 1000 K\leq1000 K1000


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 ii+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条边的方案数

但是,一般考路径矩阵加速都很考验建图技巧

P5789 TJOI2017 可乐(数据加强版)

加里敦星球的人们特别喜欢喝可乐。因而,他们的敌对星球研发出了一个可乐机器人,并且放在了加里敦星球的 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,m100 , $ 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;
}

P2579 ZJOI2005 沼泽鳄鱼

潘塔纳尔沼泽地号称世界上最大的一块湿地,它地位于巴西中部马托格罗索州的南部地区。每当雨季来临,这里碧波荡漾、生机盎然,引来不少游客。

为了让游玩更有情趣,人们在池塘的中央建设了几座石墩和石桥,每座石桥连接着两座石墩,且每两座石墩之间至多只有一座石桥。这个景点造好之后一直没敢对外开放,原因是池塘里有不少危险的食人鱼。

豆豆先生酷爱冒险,他一听说这个消息,立马赶到了池塘,想做第一个在桥上旅游的人。虽说豆豆爱冒险,但也不敢拿自己的性命开玩笑,于是他开始了仔细的实地勘察,并得到了一些惊人的结论:食人鱼的行进路线有周期性,这个周期只可能是 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 N1 的整数编号。

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 0x,yN1,表示这座石桥连接着编号为 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 1N50 1 ≤ K ≤ 2 × 1 0 9 1 \leq K \leq 2 \times 10^9 1K2×109 1 ≤ N F i s h ≤ 20 1 \leq \mathrm{NFish} \leq 20 1NFish20

当我们写多了邻接矩阵题,会发现邻接矩阵可以加速的条件是每个时刻的边都相同(即邻接矩阵相同),但是本题因为有食人鱼所以有时候有些边走不了,**矩阵会随时间变化!**怎么办?

看了一下,T只有三种情况:2、3、4,而且是==呈现周期规律==,这时我们可以得出12为2、3、4的公倍数,每相邻的十二次操作答案都是一样的,所以我们可以先预处理前十二次操作,设 K = 12 ∗ q + p ( p ≤ 11 ) K=12*q+p(p\leq11) K=12q+p(p11),前面 12 ∗ q 12*q 12q次操作可以加速,然后再单独乘上前面 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;
}

P4159 SCOI2009 迷路

该有向图有 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 2n10 1 ≤ t ≤ 1 0 9 1 \leq t \leq 10^9 1t109

这题看上去有点板子,但是我们发现它和我们板子的每条边用时都为1不同,这题边的时间是 1 − 9 1-9 19,这时就用到

套路——拆点:把每个点x拆成九个假点(分别表示距离目的地还有 0 − 8 0-8 08次操作),我们可以给它编号 x ∗ 9 + i x*9+i x9+i

  • 每次操作,每个假点都会向前一个假点连边 a d d ( x ∗ 9 + i , x ∗ 9 + i − 1 ) add(x*9+i,x*9+i-1) add(x9+i,x9+i1)
  • 每一条边 u − v ( 用时 t ) u-v(用时t) uv(用时t),就是建边 a d d ( u ∗ 9 + 0 , v ∗ 9 + t − 1 ) add(u*9+0,v*9+t-1) add(u9+0,v9+t1)
#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 N50 M ≤ 60 M \le 60 M60 t ≤ 2 30 t \le 2^{30} t230 0 ≤ A , B 0 \le A,B 0A,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;
}
  • 16
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值