线性代数(矩阵)进阶总结

jx day2 总结

根本没有进阶啊,我感觉和昨天昨天知识没啥关系。。。

就是个矩阵乘法讲了一上午,后面题目挺难。。。

先讲讲矩阵是咋乘的

矩阵乘法的必要条件是第一个矩阵的列数要等于第二个矩阵的行数

就是一个 n ∗ m n*m nm大小的矩阵乘以一个 m ∗ p m*p mp大小的矩阵会得到一个 n ∗ p n*p np的矩阵

显然如果一个 1 ∗ n 1*n 1n的向量乘以一个 n ∗ n n*n nn的方阵,得到的依然是一个 1 ∗ n 1*n 1n的向量

设答案矩阵 a n s ans ans,则 a n s i j = ∑ k = 1 m a i k ∗ b k j ans_{ij}=\sum\limits_{k=1}^ma_{ik}*b_{kj} ansij=k=1maikbkj

众所周知,矩阵满足结合律和分配律,但是不满足交换律,即 A ∗ B A*B AB不一定等于 B ∗ A B*A BA

矩阵的幂被定义成一个矩阵自己称自己 k k k次,例如 A k = A ∗ A ∗ . . . ∗ A A^k=A*A*...*A Ak=AA...A

那么因为矩阵满足结合律,所以我们可以通过快速幂加速矩阵乘法

于是,矩阵可以加速递推

那最朴实的斐波那契数列举例: f i = f i − 1 + f i − 2 f_i=f_{i-1}+f_{i-2} fi=fi1+fi2

n n n 1 0 9 10^9 109或更大的时候,我们怎么办呢?

我们发现, f i f_i fi仅与它前两项有关,所以我们构造一个一维的向量 [ a 1 a 0 ] \begin{bmatrix} a_1 \\ a_0 \\ \end{bmatrix} [a1a0] (其中 a 1 > a 0 a_1>a_0 a1>a0)

如何能够让他做一次操作使得它变成 [ a 1 + a 0 a 1 ] \begin{bmatrix} a_1+a_0 \\ a_1 \\ \end{bmatrix} [a1+a0a1] ?

当然,我们是构造一个2*2的矩阵

通过凑的方法我们发现这个方阵可以为 [ 1 1 1 0 ] \begin{bmatrix} 1&1 \\ 1&0 \\ \end{bmatrix} [1110],我们称之为单位矩阵

于是按照管用套路,设向量为 A A A,单位矩阵为 B B B,则 A N = A ∗ B N A^N=A*B^N AN=ABN

通过矩阵快速幂能够优化到 O ( 2 3 l o g n ) O(2^3logn) O(23logn)

但是有些读者就会问,这个单位矩阵怎么处理啊?

如此,我们寻找通法

对于一个普通递推式 f i = a 1 ∗ f i − 1 + a 2 ∗ f i − 2 + a 3 ∗ f i − 3 + . . . + a k ∗ f i − k f_i=a_1*f_{i-1}+a_2*f_{i-2}+a_3*f_{i-3}+...+a_k*f_{i-k} fi=a1fi1+a2fi2+a3fi3+...+akfik

可构造一个一维的向量 [ f i f i − 1 . . . f 1 ] \begin{bmatrix} f_i \\ f_{i-1} \\ ... \\ f_1 \\ \end{bmatrix} fifi1...f1

那么单位矩阵就是 [ a 1 a 2 a 3 . . . a k − 1 a k 1 0 0 . . . 0 0 0 1 0 . . . 0 0 . . . . 0 0 0 . . . 1 0 ] \begin{bmatrix} a_1 & a_2 & a_3 & ... & a_{k-1} & a_k \\ 1 & 0 & 0 & ... & 0 & 0 \\ 0 & 1 & 0 & ... & 0 & 0 \\ & &.... & \\ 0 & 0 & 0 & ... & 1 & 0 \\ \end{bmatrix} a1100a2010a300....0............ak1001ak000,正对角线下的那条对角线为 1 1 1,其余除第一行外的节点均为 0 0 0

当然,我们发现矩阵能够优化的递推基本都有一个性质,就是都是线性齐次递推,从 a k ∗ f i − k a_k*f_{i-k} akfik发现 k + i − k = i k+i-k=i k+ik=i,和是相同的,是一个比较简单的判定方法

矩阵乘法这破玩野有什么用?

洛谷上有个模板题可以去做一下

一般出在省选用来优化递推的(而且都很恶心

举个例子 GT考试

不会KMP的同学自动跳过吧

n有 1 0 9 10^9 109,直接想好想很困难,试图先想个暴力?

我们能够很轻松的想到一个暴力:

f [ i ] [ j ] f[i][j] f[i][j]表示长串匹配到第 i i i位,短串最多可以匹配到第 j j j位的方案数

明显 a n s = ∑ i = 1 m − 1 f [ n ] [ i ] ans=\sum\limits_{i=1}^{m-1}f[n][i] ans=i=1m1f[n][i]

我们可以轻松YY出方程:

f [ i ] [ j ] = ∑ k = 0 m − 1 f [ i − 1 ] [ k ] ∗ g [ k ] [ j ] f[i][j]=\sum\limits_{k=0}^{m-1}f[i-1][k]*g[k][j] f[i][j]=k=0m1f[i1][k]g[k][j],其中 g [ k ] [ j ] g[k][j] g[k][j]表示 k k k以前的串中与模式串1到 j j j位匹配的位置数(感觉自己没说清),很明显通过KMP预处理就行

时间复杂度 O ( n ∗ m 2 ) O(n*m^2) O(nm2),TLE

如何优化?

想n这么大,连数组都开不下的题目,我们应当马上想到矩乘优化

我们发现, g [ i ] [ j ] g[i][j] g[i][j]是固定的,可以把 f [ i ] [ j ] f[i][j] f[i][j]当成一个矩阵, F [ i ] F[i] F[i]的第 i i i行的元素就是 f [ i ] [ j ] f[i][j] f[i][j]

F [ i ] = F [ i − 1 ] ∗ G F[i]=F[i-1]*G F[i]=F[i1]G G G G为固定值,矩乘优化基本式,随便做

int n, m, MOD ;
int nxt[N], match[N][50] ;
char s[N] ;

class matrix {
public:
    int a[50][50] ;
    matrix() {
        clr(a) ;
    }
    matrix operator *(matrix b) {
        matrix res ;
        clr(res.a) ;
        rep(i, 0, m - 1)
        rep(j, 0, m - 1)
        rep(k, 0, m - 1) {
            res.a[i][j] = (res.a[i][j] + a[i][k] * b.a[k][j]) % MOD ;
        }
        return res ;
    }
} f, g ;

inline matrix ksm(matrix A, int pw) {
    matrix ans ;
    rep(i, 0, m) ans.a[i][i] = 1 ;
    for (; pw; pw >>= 1, A = A * A) if (pw & 1) ans = ans * A ;
    return ans ;
}

signed main(){
    scanf("%d%d%d", &n, &m, &MOD) ;
    scanf("%s", s + 1) ;
    nxt[0] = -1 ;
    rep(i, 1, m) {
        int j = nxt[i - 1] ;
        while (j != -1 && s[j + 1] != s[i]) j = nxt[j] ;
        nxt[i] = j + 1 ;
    }
    nxt[0] = 0 ;
    rep(i, 0, m - 1)
    rep(j, '0', '9') {
        int tmp = i ;
        while (s[tmp + 1] != j && tmp) tmp = nxt[tmp] ;
        if (s[tmp + 1] == j) tmp++ ;
        if (tmp < m) match[i][tmp]++ ;
    }
    clr(f.a) ; clr(g.a) ;
    f.a[0][0] = 1 ;
    rep(i, 0, m)
    rep(j, 0, m)
    g.a[i][j] = match[i][j] ;
    g = ksm(g, n) ;
    f = f * g ;
    int ans = 0 ;
    rep(i, 0, m - 1) ans = (ans + f.a[0][i]) % MOD ;
    printf("%d\n", ans) ;
    return 0 ;
}

再看一个题[HNOI2011]数学作业

O(n)算法很显然,但是钦定TLE

显然 d p dp dp方程为 f [ i ] = f [ i − 1 ] ∗ 1 0 k + i f[i]=f[i-1]*10^k+i f[i]=f[i1]10k+i

显然就是矩乘优化了啊

但是 1 0 k 10^k 10k这玩野怎么搞啊

当然可以,我们枚举位数,从 1 1 1位数枚举到 n n n的位数,对每一个位数进行矩阵快速幂。

有可能不是整的,处理一下就好了

ll n, MOD ;

struct mat {
	ll a[5][5] ;
	mat operator = (int k) {
		a[1][1] = 1 ;
		rep(i, 1, k) a[1][1] *= 10 ;
		a[1][1] %= MOD ;
		a[1][2] = a[1][3] = 0 ;
		a[2][1] = a[2][2] = 1 ; a[2][3] = 0 ;
		a[3][1] = a[3][2] = a[3][3] = 1 ;
		return *this ;
	}
	mat operator * (mat b) {
		mat c ;
		rep(i, 1, 3)
		rep(j, 1, 3) {
			c.a[i][j] = 0 ;
			rep(k, 1, 3)
			c.a[i][j] = (c.a[i][j] + a[i][k] % MOD * b.a[k][j] % MOD) % MOD ;
		}
		return c ;
	}
	mat operator ^(ll x) {
		mat c, a ; a = *this ;
		rep(i, 1, 3)
		rep(j, 1, 3)
		c.a[i][j] = (i == j) ;
		for (; x; x >>= 1, a = a * a) if (x & 1) c = c * a ;
		return c ;
	}
} a[20], b ;

ll pow10(int x) {
	ll ans = 1 ;
	rep(i, 1, x) ans *= 10 ;
	return ans ;
}

signed main(){
	freopen("concatenate.in", "r", stdin) ;
	freopen("concatenate.out", "w", stdout) ;
	scanf("%lld%lld", &n, &MOD) ;
	int k = 0 ; ll tmp = n ;
	while (tmp) k++, tmp /= 10 ;
    rep(i, 1, k - 1) {
		a[i] = i ;
		a[i] = a[i] ^ (pow10(i - 1) * 9) ;
	}
	tmp = n - pow10(k - 1) + 1 ;
	a[k] = k ;
	a[k] = a[k] ^ tmp ;
	rep(i, 1, 3)
	rep(j, 1, 3)
	b.a[i][j] = (i == j) ;
	rep(i, 1, k) b = b * a[i] ;
	printf("%lld\n", b.a[3][1]) ;
	return 0 ;
}

没了就AC了这两道

之后讲一讲如何把矩阵快速幂优化到 O ( n 2 l o g n ) O(n^2logn) O(n2logn)

特征多项式

cayley
在这里插入图片描述

裸题 BZOJ 4161 Shlw loves matrixI


int n, k;
int a[MAXN], now[MAXN];
int res[MAXN], h[MAXN];
void times(int *x, int *y) {
	static int tmp[MAXN];
	memset(tmp, 0, sizeof(tmp));
	for (int i = 0; i <= k - 1; i++)
	for (int j = 0; j <= k - 1; j++)
		tmp[i + j] = (tmp[i + j] + 1ll * x[i] * y[j]) % P;
	for (int i = 2 * k - 2; i >= k; i--) {
		for (int j = 1; j <= k; j++)
			tmp[i - j] = (tmp[i - j] + 1ll * tmp[i] * a[j]) % P;
		tmp[i] = 0;
	}
	for (int i = 0; i <= k - 1; i++)
		x[i] = tmp[i];
}
int main() {
	read(n), read(k);
	for (int i = 1; i <= k; i++) {
		read(a[i]);
		if (a[i] < 0) a[i] += P;
	}
	for (int i = 0; i <= k - 1; i++) {
		read(h[i]);
		if (h[i] < 0) h[i] += P;
	}
	if (n <= k - 1) {
		writeln(h[k]);
		return 0;
	}
	n -= k - 1; res[0] = now[1] = 1;
	while (n != 0) {
		if (n & 1) times(res, now);
		n >>= 1;
		times(now, now);
	}
	for (int i = k; i <= 2 * k - 2; i++)
	for (int j = 1; j <= k; j++)
		h[i] = (h[i] + 1ll * h[i - j] * a[j]) % P;
	int ans = 0;
	for (int i = 0; i <= k - 1; i++)
		ans = (ans + 1ll * h[k - 1 + i] * res[i]) % P;
	writeln(ans);
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值