数据结构维护矩阵乘法
水博客 ing
引入:矩阵乘法
应用
题目中统计 dp 值等信息时,统计方式可以使用矩阵表达,然后多次进行统计相当于多次乘上某个矩阵。而对应的矩阵乘法(可能式广义矩阵乘法,如 )满足结合律时,可以使用某些方法优化矩阵积的统计,例如快速幂或者线段树优化矩阵乘法。(注意有些时候矩阵乘法不满足交换律,需要注意矩阵乘法的顺序)
数据结构(一般会是线段树)维护矩阵乘法应用较为广泛,在很多时候均有用到(某些矩阵会进行变化,使用线段树维护区间变化就很方便;然而视要求也可以 平衡树维护矩阵乘法),实现难度不等,从 ABC246H 01? Queries 到 CF1368H2 Breadboard Capacity (hard version) 都涉及到了矩阵乘法,但是某些时候修改/统计矩阵的方式不同造成了代码难度的差异。
在对区间的矩阵整体进变化时(e.g. 区间反转),可以维护区间内操作/未操作对应的矩阵积,修改时直接 swap 对应的矩阵积即可。
当然可以把矩阵乘法维护的信息放到树上。(e.g. P8820 [CSP-S 2022] 数据传输)
例题1:P7739 [NOI2021] 密码箱
解法
首先考虑以 形式表示的 能不能直接展开而不进行约分。若这样写的 不用约分,则 同样不用约分, 也不用约分;如果将 写成 的形式,归纳法可证明 可以不用约分。
考虑 如何快速求出。由于我们要分别计算分子分母,考虑某个 值对 的分子分母各自的贡献。设 的分数形式为 ,则 的分数形式为 ,考虑将 的分数形式写成矩阵:将 写为 , 写为 ,则 。这样计算答案就是区间矩阵积的形式(并且是从左乘到右)。
接着考虑把 操作对应的矩阵 写出来。由题得 ,,;可得 。
使用文艺平衡树维护矩阵积,最后计算 矩阵积 即可。(,以此避免维护 除最后一项外的对应矩阵积和最后一项本身)
似乎有 可以不需要维护四种矩阵对应的乘积的方法。
代码
不太长。(可能是因为写过了一遍)
点此查看代码
#include <bits/stdc++.h> using namespace std; const int maxn=100010; const int md=998244353; mt19937 Rand(time(0)); int n,q,i,l,r,x,y,z,rt,tot; char s[maxn],c; struct mat{ int xx,xy,yx,yy; inline mat operator *(const mat &a){ return (mat){(1LL*xx*a.xx+1LL*xy*a.yx)%md, (1LL*xx*a.xy+1LL*xy*a.yy)%md, (1LL*yx*a.xx+1LL*yy*a.yx)%md, (1LL*yx*a.xy+1LL*yy*a.yy)%md}; } }; const mat M[2]={{1,0,1,1},{2,1,md-1,0}}; struct node{ int ls,rs,siz; unsigned key; bool rev,flp,val; mat mul,rem,flm,rfm; }tr[maxn<<1]; #define ls(p) tr[p].ls #define rs(p) tr[p].rs #define siz(p) tr[p].siz #define key(p) tr[p].key #define rev(p) tr[p].rev #define flp(p) tr[p].flp #define val(p) tr[p].val #define mul(p) tr[p].mul #define rem(p) tr[p].rem #define flm(p) tr[p].flm #define rfm(p) tr[p].rfm inline int New(bool f){ tr[++tot]={0,0,1,Rand(),0,0,f,M[f],M[!f],M[f],M[!f]}; return tot; } inline void PushFlp(int p){ if(!p) return; flp(p)^=1; swap(ls(p),rs(p)); swap(mul(p),flm(p)); swap(rem(p),rfm(p)); } inline void PushRev(int p){ if(!p) return; rev(p)^=1; val(p)^=1; swap(mul(p),rem(p)); swap(flm(p),rfm(p)); } inline void Pushdown(int p){ if(flp(p)){ PushFlp(ls(p)); PushFlp(rs(p)); flp(p)=0; } if(rev(p)){ PushRev(ls(p)); PushRev(rs(p)); rev(p)=0; } } inline void Pushup(int p){ siz(p)=siz(ls(p))+siz(rs(p))+1; mul(p)=flm(p)=M[val(p)]; rem(p)=rfm(p)=M[!val(p)]; if(ls(p)){ mul(p)=mul(ls(p))*mul(p); rem(p)=rem(ls(p))*rem(p); flm(p)=flm(p)*flm(ls(p)); rfm(p)=rfm(p)*rfm(ls(p)); } if(rs(p)){ mul(p)=mul(p)*mul(rs(p)); rem(p)=rem(p)*rem(rs(p)); flm(p)=flm(rs(p))*flm(p); rfm(p)=rfm(rs(p))*rfm(p); } } void sSplit(int p,int s,int &x,int &y){ if(!p){ x=y=0; return; } Pushdown(p); if(siz(ls(p))<s){ x=p; sSplit(rs(p),s-siz(ls(p))-1,rs(p),y); } else{ y=p; sSplit(ls(p),s,x,ls(p)); } Pushup(p); } int Merge(int x,int y){ if(!(x&&y)) return x|y; if(key(x)<key(y)){ Pushdown(x); rs(x)=Merge(rs(x),y); Pushup(x); return x; } else{ Pushdown(y); ls(y)=Merge(x,ls(y)); Pushup(y); return y; } } inline void Ans(){ printf("%d %d\n",mul(rt).xx, (mul(rt).xx+mul(rt).yx)%md); } int main(){ scanf("%d%d%s",&n,&q,s+1); for(i=1;i<=n;++i) rt=Merge(rt,New(s[i]=='E')); Ans(); while(q--){ scanf("%s",s+1); if(s[1]=='A'){ scanf(" %c",&c); rt=Merge(rt,New(c=='E')); } else{ scanf("%d%d",&l,&r); sSplit(rt,r,y,z); sSplit(y,l-1,x,y); if(s[1]=='F') PushRev(y); else PushFlp(y); rt=Merge(Merge(x,y),z); } Ans(); } return 0; }
例题2:P7453 [THUSCH2017] 大魔法师
解法
区间修改可看成区间乘上某个矩阵,写出矩阵乘法的形式并不难,关键在如何维护一个类似于矩阵和的形式(题目中的区间 )。
由于矩阵乘法满足分配律,则可以直接在区间上维护矩阵和,可以使用线段树维护矩阵和,还有用维护懒标记的方式维护乘上的矩阵积。
本文来自博客园,作者:Fran-Cen,转载请注明原文链接:https://www.cnblogs.com/Fran-CENSORED-Cwoi/p/16811491.html
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 【杂谈】分布式事务——高大上的无用知识?