题解:【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} \]

对于每一个操作,我们只需要用线段树的区间乘法,只不过改成乘一个矩阵就好。

询问则是一个区间求和,把要求的区间里的矩阵加起来就好了。

技巧(正题)

这道题的具体做法其他题解可能讲的更为清晰,所以我来总结一些以后编程中应该尽量去使用的小技巧。

本题其实不卡常。

先上一张图:
QQ图片20220823194004.png

那么我做了些什么呢?

不要在意全是 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);
} 
posted @ 2022-08-24 17:59  LgxTpre  阅读(76)  评论(0编辑  收藏  举报