数学/数论专题-专项训练:矩阵相关#1

1. 前言

本篇文章是作者学习矩阵的时候的一些相关训练。

注意作者是个 OIer,因此并不会涉及到线性代数知识(或者说是很少)。

前置知识:矩阵快速幂

2. 题单

题单:

P5343 【XR-1】分块

显然这道题并不是分块

首先我们需要预处理出来有哪些块长是 xht37 可以分的,这里记作 a a a 数组,其大小为 c n t cnt cnt注意去重


那么接下来考虑 60pts 的部分分。

f i f_i fi 表示将前 i i i 个数按照所给块长分块的方案数,那么我们有转移方程:

f i = ∑ 1 ≤ j ≤ c n t , i − a j ≥ 0 f i − a j f_{i}=\sum_{1 \leq j \leq cnt,i-a_{j} \geq 0}f_{i-a_j} fi=1jcnt,iaj0fiaj

初值: f 0 = 1 f_0=1 f0=1

这个转移方程是比较容易推的,有 CSP-J 一等的实力就能推出来。

发现这个复杂度是 O ( n ) O(n) O(n),60pts 到手。


那么如何处理 n ≤ 1 0 18 n \leq 10^{18} n1018 的数据呢?

仔细看看这个转移方程,我们发现实际上该方程是线性转移的,因此可以采用矩阵来优化。

什么是线性转移?线性转移就是满足 f i = ∑ j 满 足 某 条 件 f j f_i=\sum_{j满足某条件}f_j fi=jfj 这样的式子,换言之就是有个通式。

由于 x ≤ 100 x \leq 100 x100,所以首先考虑暴力求出 f 1...100 f_{1...100} f1...100

然后构造一个矩阵 [ f 1 f 2 … f 100 ] \begin{bmatrix}f_1\\f_2\\\dots\\f_{100}\end{bmatrix} f1f2f100

那么如何转移呢?

我们需要从 [ f k f k + 1 … f k + 99 ] \begin{bmatrix}f_k\\f_{k+1}\\\dots\\f_{k+99}\end{bmatrix} fkfk+1fk+99 转移到 [ f k + 1 f k + 2 … f k + 100 ] \begin{bmatrix}f_{k+1}\\f_{k+2}\\\dots\\f_{k+100}\end{bmatrix} fk+1fk+2fk+100,那么前 99 行需要原封不动的转移,对于第 100 行的转移我们需要根据 a i a_i ai 构造线性递推式转移。

具体而言就是这样(转移矩阵为 B a s e Base Base):

对于 i ∈ [ 1 , 99 ] i \in[1,99] i[1,99] B a s e i + 1 , i = 1 Base_{i+1,i}=1 Basei+1,i=1

对于 i = 100 i=100 i=100 B a s e 100 , 100 − a j + 1 = 1 ∣ j ∈ [ 1 , c n t ] Base_{100,100-a_j+1}=1|j \in[1,cnt] Base100,100aj+1=1j[1,cnt]

其余均为 0。

这样就可以转移啦~

不理解的读者可以通过样例手造矩阵理解一下qwq

我们的最终目的是 f n f_{n} fn,那么我们只需要计算以下结果:

[ f 1 f 2 … f 100 ] × B a s e n − 99 \begin{bmatrix}f_1\\f_2\\\dots\\f_{100}\end{bmatrix} \times Base^{n-99} f1f2f100×Basen99

然后取出第 100 行的项就可以了。

注意对于 n ≤ 100 n \leq 100 n100 的时候直接输出 f i f_i fi 即可。

复杂度为 O ( 10 0 3 log ⁡ n ) O(100^3 \log n) O(1003logn)


Code:

/*
========= Plozia =========
    Author:Plozia
    Problem:P5343 【XR-1】分块
    Date:2021/6/10
========= Plozia =========
*/

#include <bits/stdc++.h>
#define int long long

typedef long long LL;
const int MAXN = 100 + 10, P = 1e9 + 7;
int a[MAXN], PR, aPR[MAXN], NF, aNF[MAXN], cnt, f[MAXN];
LL n;
struct Matrix
{
    int a[MAXN][MAXN], r, c;
    void init()
    {
        memset(a, 0, sizeof(a));
        for (int i = 1; i <= r; ++i)
            a[i][i] = 1;
    }
    Matrix operator *(const Matrix &fir)
    {
        Matrix tmp; memset(tmp.a, 0, sizeof(tmp.a));
        tmp.r = r; tmp.c = fir.c;
        for (int i = 1; i <= r; ++i)
            for (int k = 1; k <= c; ++k)
            {
                int t = a[i][k];
                for (int j = 1; j <= fir.c; ++j)
                    { tmp.a[i][j] += t * fir.a[k][j]; tmp.a[i][j] %= P; }
            }
        return tmp;
    }
}Base;

int Read()
{
    int sum = 0, fh = 1; char ch = getchar();
    for (; ch < '0' || ch > '9'; ch = getchar()) fh -= (ch == '-') << 1;
    for (; ch >= '0' && ch <= '9'; ch = getchar()) sum = sum * 10 + ch - '0';
    return sum * fh;
}

void init()
{
    int l = 1, r = 1;
    while (l <= PR && r <= NF)
    {
        if (aPR[l] == aNF[r]) { ++l; ++r; ++cnt; a[cnt] = aPR[l - 1]; }
        else if (aPR[l] > aNF[r]) ++r;
        else ++l;
    }
    cnt = std::unique(a + 1, a + cnt + 1) - (a + 1);
    f[0] = 1;
    for (int i = 1; i <= 100; ++i)
        for (int j = 1; j <= cnt; ++j)
        {
            if (i < a[j]) continue ;
            f[i] += f[i - a[j]]; f[i] %= P;
        }
}

Matrix ksm(Matrix fir, LL sec, LL P)
{
    Matrix ans; ans.r = ans.c = fir.r; ans.init();
    for (; sec; sec >>= 1, fir = fir * fir)
        if (sec & 1) ans = ans * fir;
    return ans;
}

signed main()
{
    scanf("%lld", &n); PR = Read();
    for (int i = 1; i <= PR; ++i) aPR[i] = Read();
    NF = Read();
    for (int i = 1; i <= NF; ++i) aNF[i] = Read();
    std::sort(aPR + 1, aPR + PR + 1);
    std::sort(aNF + 1, aNF + NF + 1);
    init(); Base.r = Base.c = 100;
    memset(Base.a, 0, sizeof(Base.a));
    for (int i = 1; i <= 99; ++i) Base.a[i + 1][i] = 1;
    for (int i = 1; i <= cnt; ++i) Base.a[100 - a[i] + 1][100] = 1;
    if (n <= 100) { printf("%d\n", f[n]); return 0; }
    n -= 100; Matrix ans = ksm(Base, n, P);
    Matrix d; memset(d.a, 0, sizeof(d.a));
    d.r = 1; d.c = 100;
    for (int i = 1; i <= 100; ++i) d.a[1][i] = f[i];
    d = d * ans; printf("%d\n", d.a[1][100]); return 0;
}

P5789 [TJOI2017]可乐(数据加强版)

这道题有一道数据弱化版,切了这道题的各位可以顺便把弱化版给切了。

做这道题首先需要知道一个结论:

设一张无向图 G = < V , E > G=<V,E> G=<V,E> A A A 是其邻接矩阵表示是否连通,设 k ∈ N + k \in N_+ kN+,那么做矩阵快速幂 A k A^k Ak 之后 A i , j A_{i,j} Ai,j 表示 k k k 步后 i → j i \to j ij 有多少种方案。

证明可以考虑设 f i , j , k f_{i,j,k} fi,j,k k k k 步后 i → j i \to j ij 的方案数,那么有转移方程:

f i , j , k = ∑ f i , v , k − 1 × f v , j , k − 1 f_{i,j,k}=\sum f_{i,v,k-1} \times f_{v,j,k-1} fi,j,k=fi,v,k1×fv,j,k1

我们发现这就是矩阵乘法的定义式,而 f i , j , 1 f_{i,j,1} fi,j,1 就是邻接矩阵。

于是这就等价于 A k A^k Ak


考虑题目中的三种走法:

  • 移动到相邻的城市。

这个直接在邻接矩阵里面存好了。

  • 停在原地。

每个点向自己连边。

  • 自爆。

建立一个自爆节点 n + 1 n+1 n+1 [ 1 , n ] [1,n] [1,n] 内的所有点往 n + 1 n+1 n+1 连边,相当于走到这个点就不能走了,也就是自爆。

注意: n + 1 n+1 n+1 不能向自己连边。

连边之后求出 A t A^t At,答案就是 ∑ j ∈ [ 1 , n + 1 ] A 1 , j t \sum_{j \in[1,n+1]} A^t_{1,j} j[1,n+1]A1,jt


Code:

/*
========= Plozia =========
    Author:Plozia
    Problem:P5789 [TJOI2017]可乐(数据加强版)
    Date:2021/6/11
========= Plozia =========
*/

#include <bits/stdc++.h>

typedef long long LL;
const int MAXN = 100 + 5, P = 2017;
int n, m, t;

struct Matrix
{
    LL a[MAXN][MAXN];
    int r, c;
    void init()
    {
        memset(a, 0, sizeof(a));
        for (int i = 1; i <= r; ++i) a[i][i] = 1;
    }
    Matrix operator *(const Matrix &fir)
    {
        Matrix tmp; memset(tmp.a, 0, sizeof(tmp.a));
        tmp.r = r; tmp.c = fir.c;
        for (int i = 1; i <= r; ++i)
            for (int k = 1; k <= c; ++k)
            {
                LL t = a[i][k];
                for (int j = 1; j <= fir.c; ++j)
                    { ((tmp.a[i][j] += t * fir.a[k][j]) >= P) ? (tmp.a[i][j] %= P) : 0; }
            }
        return tmp;
    }
}Base;

int Read()
{
    int sum = 0, fh = 1; char ch = getchar();
    for (; ch < '0' || ch > '9'; ch = getchar()) fh -= (ch == '-') << 1;
    for (; ch >= '0' && ch <= '9'; ch = getchar()) sum = (sum << 3) + (sum << 1) + (ch ^ 48);
    return sum * fh;
}
int Max(int fir, int sec) { return (fir > sec) ? fir : sec; }
int Min(int fir, int sec) { return (fir < sec) ? fir : sec; }

Matrix ksm(Matrix fir, LL sec)
{
    Matrix ans; ans.r = ans.c = fir.r; ans.init();
    for (; sec; sec >>= 1, fir = fir * fir)
        if (sec & 1) ans = ans * fir;
    return ans;
}

int main()
{
    n = Read(), m = Read();
    Base.r = Base.c = n + 1; Base.init();
    for (int i = 1; i <= m; ++i)
    {
        int x = Read(), y = Read();
        Base.a[x][y] = Base.a[y][x] = 1;
    }
    for (int i = 1; i <= n; ++i) Base.a[i][n + 1] = 1;
    t = Read(); Base = ksm(Base, t);
    LL ans = 0;
    for (int i = 1; i <= n + 1; ++i) ans += Base.a[1][i];
    ans %= P; printf("%lld\n", ans); return 0;
}

P5337 [TJOI2019]甲苯先生的字符串

这道题跟上一道题是一样的。

考虑将 [ a , z ] [a,z] [a,z] 内的字母看作 26 个节点,建立邻接矩阵,题目中说的不能相邻就是不能连边,求出 A k − 1 A^{k-1} Ak1,然后求出 ∑ A i , j k − 1 \sum A^{k-1}_{i,j} Ai,jk1 即可。

注意指数是 k − 1 k-1 k1,因为实际上我们只需要走 k − 1 k-1 k1 次。

Code:

/*
========= Plozia =========
    Author:Plozia
    Problem:P5337 [TJOI2019]甲苯先生的字符串
    Date:2021/6/11
    Remarks:字符串长度为 n 意思是走 n - 1 步而不是 n 步
========= Plozia =========
*/

#include <bits/stdc++.h>
using std::string;

typedef long long LL;
const int MAXN = 26 + 5, P = 1e9 + 7;
LL n;
string str;
struct Matrix
{
    LL a[MAXN][MAXN];
    int r, c;
    void init()
    {
        memset(a, 0, sizeof(a));
        for (int i = 1; i <= r; ++i) a[i][i] = 1;
    }
    Matrix operator *(const Matrix &fir)
    {
        Matrix ans; ans.r = r; ans.c = fir.c; memset(ans.a, 0, sizeof(ans.a));
        for (int i = 1; i <= r; ++i)
            for (int k = 1; k <= c; ++k)
            {
                LL t = a[i][k];
                for (int j = 1; j <= fir.c; ++j)
                { ((ans.a[i][j] += t * fir.a[k][j]) >= P) ? (ans.a[i][j] %= P) : 0; }
            }
        return ans;
    }
}Base;

LL Read()
{
    LL sum = 0, fh = 1; char ch = getchar();
    for (; ch < '0' || ch > '9'; ch = getchar()) fh -= (ch == '-') << 1;
    for (; ch >= '0' && ch <= '9'; ch = getchar()) sum = (sum << 3) + (sum << 1) + (ch ^ 48);
    return sum * fh;
}
int Max(int fir, int sec) { return (fir > sec) ? fir : sec; }
int Min(int fir, int sec) { return (fir < sec) ? fir : sec; }

Matrix ksm(Matrix a, LL b)
{
    Matrix ans; ans.r = ans.c = a.r; ans.init();
    for (; b; b >>= 1, a = a * a)
        if (b & 1) ans = ans * a;
    return ans;
}

int main()
{
    n = Read(); std::cin >> str;
    Base.r = Base.c = 26;
    for (int i = 1; i <= 26; ++i)
        for (int j = 1; j <= 26; ++j)
            Base.a[i][j] = 1;
    for (int i = 0; i + 1 < str.size(); ++i)
    {
        if (str[i] == '\r' || str[i] == '\n') continue ;
        Base.a[str[i] - 'a' + 1][str[i + 1] - 'a' + 1] = 0;
    }
    Base = ksm(Base, n - 1);
    LL ans = 0;
    for (int i = 1; i <= 26; ++i)
        for (int j = 1; j <= 26; ++j)
            ((ans += Base.a[i][j]) >= P) ? (ans -= P) : 0;
    printf("%lld\n", ans); return 0;
}

3. 总结

本篇博文的题都比较套路,就是推 DP 方程然后矩阵优化,综合性较弱。

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值