首页 > 其他分享 >题解:【THUSCH2017】 大魔法师

题解:【THUSCH2017】 大魔法师

时间:2022-08-24 18:14:57浏览次数:86  
标签:begin end 题解 矩阵 times 魔法师 Base bmatrix THUSCH2017

【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);
} 

标签:begin,end,题解,矩阵,times,魔法师,Base,bmatrix,THUSCH2017
From: https://www.cnblogs.com/LittleTwoawa/p/16621116.html

相关文章

  • 题解:【POI2001】Goldmine
    【POI2001】Goldmine题目链接扫描线板子题,本质上这个题跟窗口的星星是一样的,只不过把权值$k$都换成$1$就行。但是需要注意的是这句话:(若矿石位于这块地的边缘,我们同......
  • 数的划分 题解
    \(0.\)写在前面1.3【例题1】数的划分-TuringEDUP2706数的划分-TopsCoding这题可以有两种写法:(至少两种)深搜计数\(\text{DP}\)接下来将会依次讲解\(1.\)......
  • 题解:【WC2005】双面棋盘
    【WC2005】双面棋盘题目链接这天做双面棋盘这道题,发现题解里面大多都是LCT,对于线段树套并查集的写法思路讲评很少而且不大清晰,因此有了这一篇题解。维护联通块的数量,......
  • has been blocked by CORS policy跨域问题解决
    title:hasbeenblockedbyCORSpolicy跨域问题解决我们在前端调用接口时,浏览器有时候会报错:XXXXformXXXXXhasbeenblockedbyCORSpolicy:No'Access-Control-......
  • CF1715F Crop Squares 题解
    CF1715FCropSquaressolution有一个\(n\timesm\)的长方形,四个角的坐标分别为$(0,0)$,$(0,m)$,$(n,0)$,$(n,m)$。在长方形里面有一个\(1\time......
  • CF838D题解
    很厉害的题。考虑将原本的座位串成一个环,然后加一个节点在\(1\)的前面\(n\)的后面。原问题等价为新节点不被座到的方案数。容易发现所有节点被座到和不被座到的方案......
  • 关于npm ERR! ERESOLVE could not resolve 问题解决
    1、问题描述从代码仓库拉取代码到本地,执行npminstall命令安装项目依赖,提示如下图错误  问题出现的原因由于npm版本问题,npm不同版本库之间命令不兼容。解决办法:执......
  • App 自动化测试实战技巧与经典面试题解析
    ⬇️点击“下方链接”,提升测试核心竞争力!>>更多技术文章分享和免费资料领取移动互联网时代,为了高效应对App多端发布、多版本发布、多机型发布等质量挑战,App自动化测试......
  • [题解]轮流拿牌问题_一道博弈论笔试题(C++)
    题目A和B轮流从一个数组左右两端取数,A先B后,每次取一个数,最终取数总和大者获胜,两人每次都会选择最有利的策略,求获胜者取数的和。思路笔试时遇到的一道算法题,也是博弈论中......
  • ARC099F题解
    被杀了,记录一下好了。对于他那个数组是否相等,直接判断复杂度很高,考虑通过哈希映射之后判断是否相等。对数组的Hash可以类似字符串Hash那样去做。于是判断一个区间是......