数据结构维护矩阵乘法

水博客 ing

引入:矩阵乘法

应用

题目中统计 dp 值等信息时,统计方式可以使用矩阵表达,然后多次进行统计相当于多次乘上某个矩阵。而对应的矩阵乘法(可能式广义矩阵乘法,如 Ai,j=mink(Bi,k+Ck,j))满足结合律时,可以使用某些方法优化矩阵积的统计,例如快速幂或者线段树优化矩阵乘法。(注意有些时候矩阵乘法不满足交换律,需要注意矩阵乘法的顺序)

数据结构(一般会是线段树)维护矩阵乘法应用较为广泛,在很多时候均有用到(某些矩阵会进行变化,使用线段树维护区间变化就很方便;然而视要求也可以 平衡树维护矩阵乘法),实现难度不等,从 ABC246H 01? QueriesCF1368H2 Breadboard Capacity (hard version) 都涉及到了矩阵乘法,但是某些时候修改/统计矩阵的方式不同造成了代码难度的差异。

在对区间的矩阵整体进变化时(e.g. 区间反转),可以维护区间内操作/未操作对应的矩阵积,修改时直接 swap 对应的矩阵积即可。

当然可以把矩阵乘法维护的信息放到树上。(e.g. P8820 [CSP-S 2022] 数据传输

例题1:P7739 [NOI2021] 密码箱

解法

首先考虑以 al+1al+1+1+ar1+1ar 形式表示的 f(l,r) 能不能直接展开而不进行约分。若这样写的 f(l+1,r) 不用约分,则 1f(l+1,r) 同样不用约分,al+1f(l+1,r) 也不用约分;如果将 f(r,r) 写成 ar1 的形式,归纳法可证明 f(l,r) 可以不用约分。

考虑 f(l,r) 如何快速求出。由于我们要分别计算分子分母,考虑某个 a 值对 f 的分子分母各自的贡献。设 f(l+1,r) 的分数形式为 xy,则 f(l,r) 的分数形式为 alx+yx,考虑将 f 的分数形式写成矩阵:将 f(l+1,r) 写为 [xy]f(l,r) 写为 [alx+yx],则 [al110]×[xy]=[alx+yx]。这样计算答案就是区间矩阵积的形式(并且是从左乘到右)。

接着考虑把 W,E 操作对应的矩阵 MW,ME 写出来。由题得 [a110]×MW=[a+1110][a110]×[1110]×ME=[a+1110]×[1110][b+1110]×ME=[b110]×[1110]×[1110];可得 MW=[0111],ME=[2110]

使用文艺平衡树维护矩阵积,最后计算 [1011]× 矩阵积 ×[10] 即可。([a110]×[10]=[a1],以此避免维护 a 除最后一项外的对应矩阵积和最后一项本身)

似乎有 可以不需要维护四种矩阵对应的乘积的方法

代码

不太长。(可能是因为写过了一遍)

点此查看代码
#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] 大魔法师

解法

区间修改可看成区间乘上某个矩阵,写出矩阵乘法的形式并不难,关键在如何维护一个类似于矩阵和的形式(题目中的区间 A,B,C)。

由于矩阵乘法满足分配律,则可以直接在区间上维护矩阵和,可以使用线段树维护矩阵和,还有用维护懒标记的方式维护乘上的矩阵积。

posted @   Fran-Cen  阅读(61)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 【杂谈】分布式事务——高大上的无用知识?
点击右上角即可分享
微信分享提示