【THUSCH2017】 大魔法师
前言
线段树和矩阵乘法的板子拼接题,这个题题目本身思维难度不大,但是可以给我们提供许多平时写代码的底层优化技巧。
题目思路
首先回到题目,我们需要维护 $n$ 个魔法球,每个魔法球里面的内容有水火土三部分。第四到六个操作我们还需要维护一个系数 $x$,表示这个区域有多少个水晶球。这样的话我们建一棵线段树,在每个节点维护两个 $1 \times 4$ 的矩阵,一个是当前状态 $val$,另一个是懒惰标记 $tag$。
$$
\begin{bmatrix}
A_i ; B_i ; C_i ; x
\end{bmatrix}
$$
然后我们推一下转移矩阵:
操作一:
$$
\begin{bmatrix}
A_i ; B_i ; C_i ; x
\end{bmatrix}
\times
Base
\begin{bmatrix}
A_i + B_i ; B_i ; C_i ; x
\end{bmatrix}
$$
$$
Base
\begin{bmatrix}
1 ; 0 ; 0 ; 0 \
1 ; 1 ; 0 ; 0 \
0 ; 0 ; 1 ; 0 \
0 ; 0 ; 0 ; 1 \
\end{bmatrix}
$$
操作二:
$$
\begin{bmatrix}
A_i ; B_i ; C_i ; x
\end{bmatrix}
\times
Base
\begin{bmatrix}
A_i ; B_i+C_i ; C_i ; x
\end{bmatrix}
$$
$$
Base
\begin{bmatrix}
1 ; 0 ; 0 ; 0 \
0 ; 1 ; 0 ; 0 \
0 ; 1 ; 1 ; 0 \
0 ; 0 ; 0 ; 1 \
\end{bmatrix}
$$
操作三:
$$
\begin{bmatrix}
A_i ; B_i ; C_i ; x
\end{bmatrix}
\times
Base
\begin{bmatrix}
A_i ; B_i ; C_i+A_i ; x
\end{bmatrix}
$$
$$
Base
\begin{bmatrix}
1 ; 0 ; 1 ; 0 \
0 ; 1 ; 0 ; 0 \
0 ; 0 ; 1 ; 0 \
0 ; 0 ; 0 ; 1 \
\end{bmatrix}
$$
操作四:
$$
\begin{bmatrix}
A_i ; B_i ; C_i ; x
\end{bmatrix}
\times
Base
\begin{bmatrix}
A_i + v ; B_i ; C_i ; x
\end{bmatrix}
$$
$$
Base
\begin{bmatrix}
1 ; 0 ; 0 ; 0 \
0 ; 1 ; 0 ; 0 \
0 ; 0 ; 1 ; 0 \
v ; 0 ; 0 ; 1 \
\end{bmatrix}
$$
操作五:
$$
\begin{bmatrix}
A_i ; B_i ; C_i ; x
\end{bmatrix}
\times
Base
\begin{bmatrix}
A_i ; B_i \times v ; C_i ; x
\end{bmatrix}
$$
$$
Base
\begin{bmatrix}
1 ; 0 ; 0 ; 0 \
0 ; v ; 0 ; 0 \
0 ; 0 ; 1 ; 0 \
0 ; 0 ; 0 ; 1 \
\end{bmatrix}
$$
操作六:
$$
\begin{bmatrix}
A_i ; B_i ; C_i ; x
\end{bmatrix}
\times
Base
\begin{bmatrix}
A_i ; B_i ; v ; x
\end{bmatrix}
$$
$$
Base
\begin{bmatrix}
1 ; 0 ; 0 ; 0 \
0 ; 1 ; 0 ; 0 \
0 ; 0 ; 0 ; 0 \
0 ; 0 ; v ; 1 \
\end{bmatrix}
$$
对于每一个操作,我们只需要用线段树的区间乘法,只不过改成乘一个矩阵就好。
询问则是一个区间求和,把要求的区间里的矩阵加起来就好了。
技巧(正题)
这道题的具体做法其他题解可能讲的更为清晰,所以我来总结一些以后编程中应该尽量去使用的小技巧。
本题其实不卡常。
先上一张图:
那么我做了些什么呢?
不要在意全是 WA。
首先第一个是把矩阵下标从 $1 \sim 4$ 改到 $0 \sim 3$。但是实际上作用不大。
然后抛开矩阵乘法不看,如果只看线段树,那就是一个支持区间乘,区间求和的线段树板子。甚至比线段树二简单。但是为什么它跑的这么慢呢?就是因为每一个操作都是矩阵乘法,如果普通写是一个三重循环,而平常的线段树上传下传操作是 $O(1)$ 的,因此我们优化的地方就分成两部分,一个是加速它的矩阵操作,一个是减少它的操作次数。
对于前者,这个题是一个 $1 \times 4$ 和 $4 \times 4$ 的矩阵相乘,我们把最内层循环展开,这样就减少了循环次数。
普通的矩阵乘法:
matrix operator * (const matrix& T) const
{
matrix res;
for(int i=1;i<=4;i++)
for(int j=1;j<=4;j++)
for(int k=1;k<=4;k++)
res.a[i][j]=(res.a[i][j]+(1ll*a[i][k]*T.a[k][j])%mod)%mod;
return res;
}
对于这个题的循环展开:
matrix operator * (const matrix& T) const
{
matrix res;
for(int i=0;i<4;i++)
for(int j=0;j<4;j++)
res.a[i][j]=((((1ll*a[i][0]*T.a[0][j]%mod)+1ll*a[i][1]*T.a[1][j]%mod)%mod+1ll*a[i][2]*T.a[2][j]%mod)%mod+1ll*a[i][3]*T.a[3][j]%mod)%mod;
return res;
}
对于后者,我们容易想到平时的线段树操作是在运行标记下传的时候,如果标记里面没有东西就不用运行了,这个题我们可以再额外维护一个变量 $lzy$,记录这个节点有没有标记,如果这个变量为 $0$ 就可以不用运行了。此外还可以多判断一个内容,就是如果已经到了叶子节点就不用往后传 $tag$ 了。
还要注意的一些小细节有 $tag$ 矩阵清零是要都设为单位矩阵 $I$,还有变量都设置为 int
就行,当有风险溢出时乘一个 1ll
就好了。
$Code$
#include<bits/stdc++.h>
#define MAX 250010
#define ll long long
#define mod 998244353
using namespace std;
inline int read()
{
int s=0,w=1;
char c=getchar();
while(!isdigit(c)) {if(c=='-') w=-1; c=getchar();}
while(isdigit(c)) s=(s<<1)+(s<<3)+(c^48),c=getchar();
return s*w;
}
int n,m;
int op,x,y;
int a[MAX][4];
struct matrix
{
int a[4][4];
inline matrix()
{
for(int i=0;i<4;i++)
for(int j=0;j<4;j++)
a[i][j]=0;
}
matrix operator * (const matrix& T) const
{
matrix res;
for(int i=0;i<4;i++)
for(int j=0;j<4;j++)
res.a[i][j]=((((1ll*a[i][0]*T.a[0][j]%mod)+1ll*a[i][1]*T.a[1][j]%mod)%mod+1ll*a[i][2]*T.a[2][j]%mod)%mod+1ll*a[i][3]*T.a[3][j]%mod)%mod;
return res;
}
matrix operator + (const matrix& T) const
{
matrix res;
for(int i=0;i<4;i++)
for(int j=0;j<4;j++)
res.a[i][j]=(a[i][j]+T.a[i][j])%mod;
return res;
}
};
matrix I,A,B,C,D,E,F;
inline void init()
{
for(int i=0;i<4;i++)
for(int j=0;j<4;j++)
if(i==j)
I.a[i][j]=1;
else
I.a[i][j]=0;
A.a[0][0]=A.a[1][1]=A.a[2][2]=A.a[3][3]=A.a[1][0]=1;
B.a[0][0]=B.a[1][1]=B.a[2][2]=B.a[3][3]=B.a[2][1]=1;
C.a[0][0]=C.a[1][1]=C.a[2][2]=C.a[3][3]=C.a[0][2]=1;
D.a[0][0]=D.a[1][1]=D.a[2][2]=D.a[3][3]=1;
E.a[0][0]=E.a[2][2]=E.a[3][3]=1;
F.a[0][0]=F.a[1][1]=F.a[3][3]=1;
return;
}
struct Segment_Tree
{
int l,r,lzy;
matrix val,tag;
}t[MAX<<2];
inline void pushup(int i)
{
t[i].val.a[0][0]=(t[i<<1].val.a[0][0]+t[i<<1|1].val.a[0][0])%mod;
t[i].val.a[0][1]=(t[i<<1].val.a[0][1]+t[i<<1|1].val.a[0][1])%mod;
t[i].val.a[0][2]=(t[i<<1].val.a[0][2]+t[i<<1|1].val.a[0][2])%mod;
t[i].val.a[0][3]=(t[i<<1].val.a[0][3]+t[i<<1|1].val.a[0][3])%mod;
return;
}
inline void pushdown(int i)
{
if(!t[i].lzy) return;
t[i<<1].val=t[i<<1].val*t[i].tag;
t[i<<1|1].val=t[i<<1|1].val*t[i].tag;
t[i<<1].tag=t[i<<1].tag*t[i].tag;
t[i<<1|1].tag=t[i<<1|1].tag*t[i].tag;
t[i<<1].lzy=t[i<<1|1].lzy=1;
t[i].tag=I,t[i].lzy=0;
return;
}
void build(int i,int l,int r)
{
t[i].l=l,t[i].r=r,t[i].tag=I;
if(l==r)
{
t[i].val.a[0][0]=a[l][1]%mod;
t[i].val.a[0][1]=a[l][2]%mod;
t[i].val.a[0][2]=a[l][3]%mod;
t[i].val.a[0][3]=1;
return;
}
int mid=(l+r)>>1;
build(i<<1,l,mid);
build(i<<1|1,mid+1,r);
pushup(i);
return;
}
void change(int i,int l,int r,matrix k)
{
if(t[i].l>=l&&t[i].r<=r)
{
t[i].val=t[i].val*k;
t[i].tag=t[i].tag*k;
t[i].lzy=1;
return;
}
pushdown(i);
int mid=(t[i].l+t[i].r)>>1;
if(l<=mid) change(i<<1,l,r,k);
if(r>mid) change(i<<1|1,l,r,k);
pushup(i);
return;
}
matrix query(int i,int l,int r)
{
if(t[i].l>=l&&t[i].r<=r) return t[i].val;
pushdown(i);
int mid=(t[i].l+t[i].r)>>1;
matrix ans;
if(l<=mid) ans=ans+query(i<<1,l,r);
if(r>mid) ans=ans+query(i<<1|1,l,r);
return ans;
}
int main()
{
init();
n=read();
for(int i=1;i<=n;i++)
a[i][1]=read(),a[i][2]=read(),a[i][3]=read();
build(1,1,n);
m=read();
for(int i=1;i<=m;i++)
{
op=read(),x=read(),y=read();
switch(op)
{
case 1:change(1,x,y,A);break;
case 2:change(1,x,y,B);break;
case 3:change(1,x,y,C);break;
case 4:D.a[3][0]=read();change(1,x,y,D);break;
case 5:E.a[1][1]=read();change(1,x,y,E);break;
case 6:F.a[3][2]=read();change(1,x,y,F);break;
case 7:matrix ans=query(1,x,y);cout<<ans.a[0][0]<<" "<<ans.a[0][1]<<" "<<ans.a[0][2]<<endl;break;
}
}
return (0-0);
}
标签:begin,end,题解,矩阵,times,魔法师,Base,bmatrix,THUSCH2017
From: https://www.cnblogs.com/LittleTwoawa/p/16621116.html