关于循环矩阵的一些理解和应用

问题引入

给定一个长度为\(n\)的环,每次对于一个位置上的数,加上它左边的数乘上\(L\),再加上右边的数乘上\(R\)。\(L,R\)是给定的常数,问执行\(s\)次之后,这个环上的每个位置上的数是多少

(计算时左边和右边的数都是上一次的数)

如果\(n \leq 100, s \leq 2^{30}\)

可以想到矩阵快速幂

构造矩阵
\[
\begin{aligned}
&\begin{bmatrix}a_1 & a_2 & \cdots & a_n\end{bmatrix}
\times \begin{bmatrix}
1 & l & 0 & 0 & \cdots & 0 & r \r & 1 & l & 0 & \cdots & 0 & 0 \\cdots & \cdots & \cdots & \cdots & \cdots & \cdots & \cdots \l & 0 & \cdots & \cdots & \cdots & r & 1
\end{bmatrix} \=\;& \begin{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}\)

\(\text{TLE}\)

但仔细看这个矩阵可以发现一个特点:它的每一行都是上一行往右移动一位得到的

不但如此,这个矩阵无论自乘多少次,都满足这个性质,所以理论上只需要维护第一行就好了

令\(k[i]\)表示矩阵第一行的第\(i\)个,那么有(假设是一个\(4 * 4\)的矩阵
\[
\begin{aligned}
k[1]'&=a[1][1]\times a[1][1] + a[1][2]\times a[2][1] + a[1][3]\times a[3][1] + a[1][4]\times a[4][1] \&=k[1]\times k[1] + k[2]\times k[4] + k[3]\times k[3] + k[4]\times k[2]
\end{aligned} \\therefore k[2] = k[1]\times k[2] + k[2]\times k[1] + k[3]\times k[4] + k[4]\times k[3]
\]
可以看出一些规律:
\[
k[x]=\sum_{(i+j-2) \% n + 1 = x} k[i] \times 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;
}

总结

形如\(\begin{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}\)优化循环矩阵乘法呢??

原文地址:https://www.cnblogs.com/cj-xxz/p/10323711.html

时间: 2024-11-10 08:22:32

关于循环矩阵的一些理解和应用的相关文章

循环矩阵乘

仅供个人查看,没有验证其正确性. 循环矩阵快速幂是可以通过一个一维数组来完成的.(我之前一直用二维数组,比较好理解) 比如说一个$5 \times 5$的循环矩阵,再乘一个$5 \times 5$的矩阵,         $\times$                      $=$            第一个用数组$a$表示,第二个用数组$b$表示,第三个用数组$c$表示, 那么我们把$a$和$b$相乘得到的$c$仍然是循环矩阵. 把所有情况都列出来: 根据矩阵乘定义:$c[1]=a[1

UVA 1386 - Cellular Automaton(循环矩阵)

UVA 1386 - Cellular Automaton 题目链接 题意:给定一个n格的环,现在有个距离d,每次变化把环和他周围距离d以内的格子相加,结果mod m,问经过k次变换之后,环上的各个数字 思路:矩阵很好想,每个位置对应周围几个位置为1,其余位置为0,但是这个矩阵有500,有点大,直接n^3去求矩阵不太合适,然后观察发现这个矩阵是个循环矩阵,循环矩阵相乘的话,只需要保存一行即可,然后用n^2的时间就足够计算了 代码: #include <stdio.h> #include <

弱题(循环矩阵1)

问题 D: 弱题 时间限制: 1 Sec  内存限制: 128 MB提交: 46  解决: 28[提交][状态][讨论版] 题目描述 有M个球,一开始每个球均有一个初始标号,标号范围为1-N且为整数,标号为i的球有ai个,并保证Σai = M. 每次操作等概率取出一个球(即取出每个球的概率均为1/M),若这个球标号为k(k < N),则将它重新标号为k + 1:若这个球标号为N,则将其重标号为1.(取出球后并不将其丢弃) 现在你需要求出,经过K次这样的操作后,每个标号的球的期望个数. 输入 第1

POJ3150—Cellular Automaton(循环矩阵)

题目链接:http://poj.org/problem?id=3150 题目意思:有n个数围成一个环,现在有一种变换,将所有距离第i(1<=i<=n)个数小于等于d的数加起来,对m取余,现在要求将所有的数都变换k次,得到的n个数的值. 思路:构造一个循环矩阵,以下这个矩阵是以样例1为例的循环矩阵. 1 1 0 0 1 1 1 1 0 0 0 1 1 1 0 0 0 1 1 1 1 0 0 1 1 我们发现n尽然达到了500,复杂度是n^3logk,过不了,我们发现这个矩阵长得很奇葩,每一行都是

FZU Problem 1692 Key problem(循环矩阵)

循环矩阵,这里有解说:http://wenku.baidu.com/link? url=zcJ-sxrj0QDqzz8xCnHTnB7gxjoNRyOZzS4_4ZA22c8Bs9inYn6vVkqTVr_w-riLa8oRnYA9SRcCZ9f4UciCUNGeNAG4dCGclYRPS18YLGa 推出第一层以下依据性质就能够得到. Problem 1692 Key problem Accept: 144    Submit: 663 Time Limit: 1000 mSec    Mem

循环矩阵

1 #include <stdio.h> 2 #include <string.h> 3 #define ll long long 4 const int maxn = 505; 5 ll n,m,d,k,a[maxn]; 6 struct mat{ // 结构体循环矩阵,原矩阵式方阵 7 ll v[maxn]; 8 mat() {memset(v,0,sizeof(v));} // 构造函数 9 mat operator*(mat c){ // 重载乘法运算符 10 mat an

hihocoder 1388 fft循环矩阵

#1388 : Periodic Signal 时间限制:5000ms 单点时限:5000ms 内存限制:256MB 描述 Profess X is an expert in signal processing. He has a device which can send a particular 1 second signal repeatedly. The signal is A0 ... An-1 under n Hz sampling. One day, the device fell

bzoj 2510: 弱题 循环矩阵

2510: 弱题 Time Limit: 10 Sec  Memory Limit: 128 MBSubmit: 124  Solved: 61[Submit][Status][Discuss] Description 有M个球,一开始每个球均有一个初始标号,标号范围为1-N且为整数,标号为i的球有ai个,并保证Σai = M. 每次操作等概率取出一个球(即取出每个球的概率均为1/M),若这个球标号为k(k < N),则将它重新标号为k + 1:若这个球标号为N,则将其重标号为1.(取出球后并不

Uva 1386 - Cellular Automaton ( 矩阵乘法 + 循环矩阵 )

Uva 1386 - Cellular Automaton ( 矩阵乘法 + 循环矩阵 ) #include <cstdio> #include <cstring> #define CLR( a, b ) memset( a, b, sizeof(a) ) int MOD; #define MAX_SIZE 500 struct Mat { int n; LL mat[MAX_SIZE][MAX_SIZE]; Mat( int _n = 0 ) { n = _n; CLR( mat