【洛谷7453】[THUSCH2017] 大魔法师(线段树+矩乘)

点此看题面

  • 有\(n\)个水晶球,第\(i\)个水晶球有三个属性\(A_i,B_i,C_i\)。
  • \(q\)次区间操作,分为七种:将\(A_i\)加上\(B_i\);将\(B_i\)加上\(C_i\);将\(C_i\)加上\(A_i\);将\(A_i\)加上\(v\);将\(B_i\)乘上\(v\);将\(C_i\)修改为\(v\);求三种属性各自的和。
  • \(n,q\le2.5\times10^5\)

线段树+矩阵乘法

一个挺有意思的技巧。

考虑我们初始在每个点维护一个矩阵:

\[\begin{bmatrix} a_i&0&0&0\\ 0&b_i&0&0\\ 0&0&c_i&0\\ 0&0&0&1 \end{bmatrix} \]

然后发现这些操作都是矩乘基本操作,只要给线段树上一段区间同时乘上一个矩阵即可。

下面分别给出每种操作对应的矩阵:

\[\begin{bmatrix} 1&0&0&0\\ 1&1&0&0\\ 0&0&1&0\\ 0&0&0&1 \end{bmatrix} \begin{bmatrix} 1&0&0&0\\ 0&1&0&0\\ 0&1&1&0\\ 0&0&0&1 \end{bmatrix} \begin{bmatrix} 1&0&1&0\\ 0&1&0&0\\ 0&0&1&0\\ 0&0&0&1 \end{bmatrix}\\ \begin{bmatrix} 1&0&0&0\\ 0&1&0&0\\ 0&0&1&0\\ v&0&0&1 \end{bmatrix} \begin{bmatrix} 1&0&0&0\\ 0&v&0&0\\ 0&0&1&0\\ 0&0&0&1 \end{bmatrix} \begin{bmatrix} 1&0&0&0\\ 0&1&0&0\\ 0&0&0&0\\ 0&0&v&1 \end{bmatrix} \]

询问就是在线段树上区间求和,然后输出每列数的和即可。

代码:\(O(64nlogn)\)

#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 250000
#define X 998244353
using namespace std;
int n,a[N+5],b[N+5],c[N+5];
namespace FastIO
{
	#define FS 100000
	#define tc() (FA==FB&&(FB=(FA=FI)+fread(FI,1,FS,stdin),FA==FB)?EOF:*FA++)
	#define pc(c) (FC==FE&&(clear(),0),*FC++=c)
	int OT;char oc,FI[FS],FO[FS],OS[FS],*FA=FI,*FB=FI,*FC=FO,*FE=FO+FS;
	I void clear() {fwrite(FO,1,FC-FO,stdout),FC=FO;}
	Tp I void read(Ty& x) {x=0;W(!isdigit(oc=tc()));W(x=(x<<3)+(x<<1)+(oc&15),isdigit(oc=tc()));}
	Ts I void read(Ty& x,Ar&... y) {read(x),read(y...);}
	Tp I void write(Ty x) {W(OS[++OT]=x%10+48,x/=10);W(OT) pc(OS[OT--]);}
	Tp I void writeln(Con Ty& x,Con Ty& y,Con Ty& z) {write(x),pc(' '),write(y),pc(' '),write(z),pc('\n');}
}using namespace FastIO;
struct M
{
	int a[4][4];I M(CI x=0) {memset(a,0,sizeof(a));for(RI i=0;i^4;++i) a[i][i]=x;}
	I Con int* operator [] (CI x) Con {return a[x];}I int* operator [] (CI x) {return a[x];}
	I M operator + (Con M& o) Con {M t;for(RI i=0;i^4;++i) for(RI j=0;j^4;++j) t[i][j]=(a[i][j]+o[i][j])%X;return t;}//矩阵加法
	I M operator * (Con M& o) Con {M t;for(RI i=0;i^4;++i)//矩阵乘法
		for(RI j=0;j^4;++j) for(RI k=0;k^4;++k) t[i][j]=(t[i][j]+1LL*a[i][k]*o[k][j])%X;return t;}
	I bool Ex() {for(RI i=0;i^4;++i) for(RI j=0;j^4;++j) if(a[i][j]^(i==j)) return 1;return 0;}//判断是不是单位矩阵
};
class SegmentTree
{
	private:
		#define PT CI l=1,CI r=n,CI rt=1
		#define LT l,mid,rt<<1
		#define RT mid+1,r,rt<<1|1
		#define PU(x) (V[x]=V[x<<1]+V[x<<1|1])
		#define PD(x) (F[x].Ex()&&(T(x<<1,F[x]),T(x<<1|1,F[x]),F[x]=M(1),0))
		#define T(x,v) (V[x]=V[x]*v,F[x]=F[x]*v)
		M V[N<<2],F[N<<2];
	public:
		I void Bd(PT)//建树
		{
			if(F[rt]=M(1),l==r) return (void)(V[rt][0][0]=a[l],V[rt][1][1]=b[l],V[rt][2][2]=c[l],V[rt][3][3]=1);
			RI mid=l+r>>1;Bd(LT),Bd(RT),PU(rt);
		}
		I void U(CI L,CI R,Con M& v,PT)//区间乘法
		{
			if(L<=l&&r<=R) return (void)T(rt,v);RI mid=l+r>>1;PD(rt);
			L<=mid&&(U(L,R,v,LT),0),R>mid&&(U(L,R,v,RT),0),PU(rt); 
		}
		I M Q(CI L,CI R,PT)//区间求和
		{
			if(L==l&&r==R) return V[rt];RI mid=l+r>>1;PD(rt);
			if(R<=mid) return Q(L,R,LT);if(L>mid) return Q(L,R,RT);return Q(L,mid,LT)+Q(mid+1,R,RT);
		}
}S;
int main()
{
	RI Qt,i;for(read(n),i=1;i<=n;++i) read(a[i],b[i],c[i]);S.Bd();
	RI op,x,y,v;M t;read(Qt);W(Qt--) switch(read(op,x,y),op)//对每种修改分别构造矩阵
	{
		case 1:(t=M(1))[1][0]=1,S.U(x,y,t);break;
		case 2:(t=M(1))[2][1]=1,S.U(x,y,t);break;
		case 3:(t=M(1))[0][2]=1,S.U(x,y,t);break;
		case 4:read(v),(t=M(1))[3][0]=v,S.U(x,y,t);break;
		case 5:read(v),(t=M(1))[1][1]=v,S.U(x,y,t);break;
		case 6:read(v),(t=M(1))[3][2]=v,t[2][2]=0,S.U(x,y,t);break;
		case 7:t=S.Q(x,y),writeln((0LL+t[0][0]+t[1][0]+t[2][0]+t[3][0])%X,
			(0LL+t[0][1]+t[1][1]+t[2][1]+t[3][1])%X,(0LL+t[0][2]+t[1][2]+t[2][2]+t[3][2])%X);break;//输出每一列的和
	}return clear(),0;
}
上一篇:基于Visual C++2010与windows SDK fo windows7开发windows7平台的tabletpc应用(1)-手写数学公式输入


下一篇:学习XSL-FO的基础知识