数学/数论专题-专项训练:矩阵相关#1
1. 前言
本篇文章是作者学习矩阵的时候的一些相关训练。
注意作者是个 OIer,因此并不会涉及到线性代数知识(或者说是很少)。
前置知识:矩阵快速幂
2. 题单
题单:
P5343 【XR-1】分块
显然这道题并不是分块
首先我们需要预处理出来有哪些块长是 xht37 可以分的,这里记作 数组,其大小为 ,注意去重。
那么接下来考虑 60pts 的部分分。
设 表示将前 个数按照所给块长分块的方案数,那么我们有转移方程:
初值:。
这个转移方程是比较容易推的,有 CSP-J 一等的实力就能推出来。
发现这个复杂度是 ,60pts 到手。
那么如何处理 的数据呢?
仔细看看这个转移方程,我们发现实际上该方程是线性转移的,因此可以采用矩阵来优化。
什么是线性转移?线性转移就是满足 这样的式子,换言之就是有个通式。
由于 ,所以首先考虑暴力求出 。
然后构造一个矩阵 。
那么如何转移呢?
我们需要从 转移到 ,那么前 99 行需要原封不动的转移,对于第 100 行的转移我们需要根据 构造线性递推式转移。
具体而言就是这样(转移矩阵为 ):
对于 ,。
对于 ,
其余均为 0。
这样就可以转移啦~
不理解的读者可以通过样例手造矩阵理解一下qwq
我们的最终目的是 ,那么我们只需要计算以下结果:
然后取出第 100 行的项就可以了。
注意对于 的时候直接输出 即可。
复杂度为 。
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]可乐(数据加强版)
这道题有一道数据弱化版,切了这道题的各位可以顺便把弱化版给切了。
做这道题首先需要知道一个结论:
设一张无向图 , 是其邻接矩阵表示是否连通,设 ,那么做矩阵快速幂 之后 表示 步后 有多少种方案。
证明可以考虑设 为 步后 的方案数,那么有转移方程:
我们发现这就是矩阵乘法的定义式,而 就是邻接矩阵。
于是这就等价于 。
考虑题目中的三种走法:
- 移动到相邻的城市。
这个直接在邻接矩阵里面存好了。
- 停在原地。
每个点向自己连边。
- 自爆。
建立一个自爆节点 , 内的所有点往 连边,相当于走到这个点就不能走了,也就是自爆。
注意: 不能向自己连边。
连边之后求出 ,答案就是 。
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]甲苯先生的字符串
这道题跟上一道题是一样的。
考虑将 内的字母看作 26 个节点,建立邻接矩阵,题目中说的不能相邻就是不能连边,求出 ,然后求出 即可。
注意指数是 ,因为实际上我们只需要走 次。
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 方程然后矩阵优化。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具