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

1. 前言

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

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

前置知识:矩阵快速幂

2. 题单

题单:

P5343 【XR-1】分块

显然这道题并不是分块

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


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

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

fi=1jcnt,iaj0fiaj

初值:f0=1

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

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


那么如何处理 n1018 的数据呢?

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

什么是线性转移?线性转移就是满足 fi=jfj 这样的式子,换言之就是有个通式。

由于 x100,所以首先考虑暴力求出 f1...100

然后构造一个矩阵 [f1f2f100]

那么如何转移呢?

我们需要从 [fkfk+1fk+99] 转移到 [fk+1fk+2fk+100],那么前 99 行需要原封不动的转移,对于第 100 行的转移我们需要根据 ai 构造线性递推式转移。

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

对于 i[1,99]Basei+1,i=1

对于 i=100Base100,100aj+1=1|j[1,cnt]

其余均为 0。

这样就可以转移啦~

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

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

[f1f2f100]×Basen99

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

注意对于 n100 的时候直接输出 fi 即可。

复杂度为 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>A 是其邻接矩阵表示是否连通,设 kN+,那么做矩阵快速幂 Ak 之后 Ai,j 表示 k 步后 ij 有多少种方案。

证明可以考虑设 fi,j,kk 步后 ij 的方案数,那么有转移方程:

fi,j,k=fi,v,k1×fv,j,k1

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

于是这就等价于 Ak


考虑题目中的三种走法:

  • 移动到相邻的城市。

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

  • 停在原地。

每个点向自己连边。

  • 自爆。

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

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

连边之后求出 At,答案就是 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] 内的字母看作 26 个节点,建立邻接矩阵,题目中说的不能相邻就是不能连边,求出 Ak1,然后求出 Ai,jk1 即可。

注意指数是 k1,因为实际上我们只需要走 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 方程然后矩阵优化。

posted @   Plozia  阅读(71)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具
点击右上角即可分享
微信分享提示