关于循环矩阵的一些理解和应用
Posted cj-xxz
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了关于循环矩阵的一些理解和应用相关的知识,希望对你有一定的参考价值。
问题引入
给定一个长度为(n)的环,每次对于一个位置上的数,加上它左边的数乘上(L),再加上右边的数乘上(R)。(L,R)是给定的常数,问执行(s)次之后,这个环上的每个位置上的数是多少
(计算时左边和右边的数都是上一次的数)
如果(n leq 100, s leq 2^{30})
可以想到矩阵快速幂
构造矩阵
[
egin{aligned}
&egin{bmatrix}a_1 & a_2 & cdots & a_nend{bmatrix}
imes egin{bmatrix}
1 & l & 0 & 0 & cdots & 0 & r
& 1 & l & 0 & cdots & 0 & 0 \cdots & cdots & cdots & cdots & cdots & cdots & cdots l & 0 & cdots & cdots & cdots & r & 1
end{bmatrix} =;& egin{bmatrix}a_1' & a_2' & cdots & a_n'end{bmatrix}
end{aligned}
]
于是开心地快速幂了
#include<cstdio>
#include<cstring>
#include<cctype>
#include<algorithm>
#define RG register
#define file(x) freopen(#x".in", "r", stdin), freopen(#x".out", "w", stdout)
#define clear(x, y) memset(x, y, sizeof(x))
inline int read()
{
int data = 0, w = 1; char ch = getchar();
while(ch != '-' && (!isdigit(ch))) ch = getchar();
if(ch == '-') w = -1, ch = getchar();
while(isdigit(ch)) data = data * 10 + (ch ^ 48), ch = getchar();
return data * w;
}
const int maxn(1010);
int n, s, L, R, _x, Pow[10], a[maxn], Mod;
namespace $
{
const int N(110);
struct Matrix
{
int n, m, a[N][N];
Matrix(int x, int y) : n(x), m(y) { memset(a, 0, sizeof a); }
int *operator [] (const int &id) { return a[id]; }
const int *operator [] (const int &id) const { return a[id]; }
};
Matrix operator * (const Matrix &a, const Matrix &b)
{
Matrix c(a.n, b.m);
for(RG int i = 0; i < a.n; i++)
for(RG int j = 0; j < a.m; j++)
for(RG int k = 0; k < b.m; k++)
c[i][k] = (1ll * c[i][k] + 1ll * a[i][j] * b[j][k]) % Mod;
return c;
}
int main()
{
Matrix S(1, n), T(n, n);
for(RG int i = 0; i < n; i++) S[0][i] = a[i];
for(RG int i = 0; i < n; i++)
{
int pre = (i - 1 + n) % n, suc = (i + 1) % n;
T[i][i] = 1, T[pre][i] = L, T[suc][i] = R;
}
while(s)
{
if(s & 1) S = S * T;
T = T * T; s >>= 1;
}
for(RG int i = 0; i < n; i++) printf("%d ", S[0][i]);
return 0;
}
}
int main()
{
Pow[0] = 1;
for(RG int i = 1; i < 10; i++) Pow[i] = Pow[i - 1] * 10;
n = read(), s = read(), L = read(), R = read(), _x = read();
for(RG int i = 0; i < n; i++) a[i] = read();
Mod = Pow[_x];
return $::main();
}
解决
可是:
(n leq 1000, s leq 2^{30})
( ext{TLE})
但仔细看这个矩阵可以发现一个特点:它的每一行都是上一行往右移动一位得到的
不但如此,这个矩阵无论自乘多少次,都满足这个性质,所以理论上只需要维护第一行就好了
令(k[i])表示矩阵第一行的第(i)个,那么有(假设是一个(4 * 4)的矩阵
[
egin{aligned}
k[1]'&=a[1][1] imes a[1][1] + a[1][2] imes a[2][1] + a[1][3] imes a[3][1] + a[1][4] imes a[4][1] &=k[1] imes k[1] + k[2] imes k[4] + k[3] imes k[3] + k[4] imes k[2]
end{aligned} \therefore k[2] = k[1] imes k[2] + k[2] imes k[1] + k[3] imes k[4] + k[4] imes k[3]
]
可以看出一些规律:
[
k[x]=sum_{(i+j-2) \% n + 1 = x} k[i] imes k[j]
]
这样复杂度就是(O(n^2log;s))了
解决啦!!!
#include<cstdio>
#include<cstring>
#include<cctype>
#include<algorithm>
#define RG register
#define file(x) freopen(#x".in", "r", stdin), freopen(#x".out", "w", stdout)
#define clear(x, y) memset(x, y, sizeof(x))
inline int read()
{
int data = 0, w = 1; char ch = getchar();
while(ch != '-' && (!isdigit(ch))) ch = getchar();
if(ch == '-') w = -1, ch = getchar();
while(isdigit(ch)) data = data * 10 + (ch ^ 48), ch = getchar();
return data * w;
}
const int maxn(1010);
int n, s, L, R, _x, Mod = 1;
int S[maxn], T[maxn];
void Mul(int S[], int T[])
{
static int c[maxn]; clear(c, 0);
for(RG int i = 1; i <= n; i++)
for(RG int j = 1; j <= n; j++)
(c[(i + j - 2) % n + 1] += 1ll * S[i] * T[j] % Mod) %= Mod;
std::copy(c + 1, c + n + 1, S + 1);
}
int main()
{
n = read(), s = read(), L = read(), R = read(), _x = read();
for(RG int i = 1; i <= _x; i++) Mod *= 10;
for(RG int i = 1; i <= n; i++) S[i] = read();
T[1] = 1, T[2] = L, T[n] = R;
for(; s; s >>= 1, Mul(T, T)) if(s & 1) Mul(S, T);
for(RG int i = 1; i <= n; i++) printf("%d ", S[i]);
return 0;
}
总结
形如(egin{bmatrix}c_1&c_2&cdots&c_{n-1}&c_n\c_2&c_3&cdots&c_n&c_1\cdots&cdots&cdots&cdots&cdots\c_n&c_1&cdots&c_{n-2}&c_{n-1}end{bmatrix})的矩阵是循环矩阵
循环矩阵的乘积仍是循环矩阵
所以我们只要维护循环矩阵的第一行
就可以(O(n^2))维护循环矩阵的乘积
通过这样一些神奇的性质,我们可以降低矩阵乘法的复杂度
来出毒瘤题
Extend
可不可以(mathrm{FFT})优化循环矩阵乘法呢??
以上是关于关于循环矩阵的一些理解和应用的主要内容,如果未能解决你的问题,请参考以下文章