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

问题引入

给定一个长度为\(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;
}

Extend

在这里

\(n\leq 5\times 10^4, s \leq 2^{30}\)

\(\mathrm{TLE}\)

这时我们想到,可不可以\(\mathrm{FFT}\)优化循环矩阵乘法呢??

可以!!!

将前面的下标从\(0\)开始
\[ k[x]=\sum_{(i+j) \% n = x} k[i] \times k[j] \]
这™不就是个卷积的形式吗???

证明看参考博客

这里面的知识点一定要好好理(ji)解(jie lun)

这里不再赘述。

所以:
\[ \begin{aligned} AB &= F\cdot diag(\hat a) \cdot F^H \cdot F \cdot diag(\hat b) \cdot F^H \\ &= F\cdot diag(\hat a) \cdot diag(\hat b) \cdot F^H \\ &= F\cdot diag(\hat a \odot \hat b) \cdot F^H \\ &= C\left( \mathcal F^{-1}(\hat a \odot \hat b)\right) \end{aligned} \]
和上面的结论是一样的。

于是用个\(NTT\)或者\(MTT\)即可

我非常友善的\(\mathrm{mod}\;998244353\)

#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(200010), Mod(998244353);
int n, s, L, R, N, r[maxn];
int S[maxn], T[maxn];

inline int fastpow(int x, int y)
{
    int ans = 1;
    while(y)
    {
        if(y & 1) ans = 1ll * ans * x % Mod;
        x = 1ll * x * x % Mod, y >>= 1;
    }
    return ans;
}

template<int opt> void FFT(int *p)
{
    for(RG int i = 0; i < N; i++) if(i < r[i]) std::swap(p[i], p[r[i]]);
    for(RG int i = 1; i < N; i <<= 1)
    {
        int rot = fastpow(3, (Mod - 1) / (i << 1));
        for(RG int j = 0; j < N; j += (i << 1))
            for(RG int k = 0, w = 1; k < i; ++k, w = 1ll * w * rot % Mod)
            {
                int x = p[j + k], y = 1ll * w * p[i + j + k] % Mod;
                p[j + k] = (x + y) % Mod, p[i + j + k] = (x - y + Mod) % Mod;
            }
    }
    if(opt == -1) std::reverse(p + 1, p + N);
}

void Mul(int *S, int *T)
{
    static int a[maxn], b[maxn];
    std::fill(a, a + N, 0); std::fill(b, b + N, 0);
    std::copy(S, S + n, a); std::copy(T, T + n, b);
    FFT<1>(a), FFT<1>(b);
    for(RG int i = 0; i < N; i++) a[i] = 1ll * a[i] * b[i] % Mod;
    FFT<-1>(a); std::fill(S, S + n, 0);
    for(RG int i = 0, inv = fastpow(N, Mod - 2); i < N; i++)
        a[i] = 1ll * a[i] * inv % Mod;
    for(RG int i = 0; i < N; i++) S[i % n] = (S[i % n] + a[i]) % Mod;
}

int main()
{
    int P = -1;
    n = read(), s = read(), L = read(), R = read();
    for(RG int i = 0; i < n; i++) S[i] = read();
    for(N = 1; N <= n + n; N <<= 1, ++P);
    for(RG int i = 0; i < N; i++) r[i] = (r[i >> 1] >> 1) | ((i & 1) << P);
    T[0] = 1, T[1] = L, T[n - 1] = R;
    for(; s; s >>= 1, Mul(T, T)) if(s & 1) Mul(S, T);
    for(RG int i = 0; 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)\)甚至\(O(nlog_2n)\)维护循环矩阵的乘积

通过这样一些神奇的性质,我们可以降低矩阵乘法的复杂度

来出毒瘤题

没了

转载于:https://www.cnblogs.com/cj-xxz/p/10323711.html

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值