PS:如果读过题了可以跳过题目描述直接到题解部分
提交链接:洛谷 P7453 [THUSCH2017] 大魔法师
题目
题目描述
大魔法师小 L 制作了 n n n 个魔力水晶球,每个水晶球有水、火、土三个属性的能量值。小 L 把这 n n n 个水晶球在地上从前向后排成一行,然后开始今天的魔法表演。
我们用 A i , B i , C i A_i,B_i,C_i Ai,Bi,Ci 分别表示从前向后第 i i i 个水晶球(下标从 1 1 1 开始)的水、火、土的能量值。
小 L 计划施展 m m m 次魔法。每次,他会选择一个区间 [ l , r ] [l,r] [l,r],然后施展以下 3 3 3 大类、 7 7 7 种魔法之一:
-
魔力激发:令区间里每个水晶球中特定属性的能量爆发,从而使另一个特定属性的能量增强。具体来说,有以下三种可能的表现形式:
- 火元素激发水元素能量:令 A i = A i + B i A_i=A_i+B_i Ai=Ai+Bi。
- 土元素激发火元素能量:令 B i = B i + C i B_i=B_i+C_i Bi=Bi+Ci。
- 水元素激发土元素能量:令 C i = C i + A i C_i=C_i+A_i Ci=Ci+Ai。
需要注意的是,增强一种属性的能量并不会改变另一种属性的能量,例如 A i = A i + B i A_i=A_i+B_i Ai=Ai+Bi 并不会使 B i B_i Bi 增加或减少。
-
魔力增强:小 L 挥舞法杖,消耗自身 v v v 点法力值,来改变区间里每个水晶球的特定属性的能量。具体来说,有以下三种可能的表现形式:
- 火元素能量定值增强:令 A i = A i + v A_i=A_i+v Ai=Ai+v。
- 水元素能量翻倍增强:令 B i = B i × v B_i=B_i\times v Bi=Bi×v。
- 土元素能量吸收融合:令 C i = v C_i=v Ci=v。
-
魔力释放:小L将区间里所有水晶球的能量聚集在一起,融合成一个新的水晶球,然后送给场外观众。生成的水晶球每种属性的能量值等于区间内所有水晶球对应能量值的代数和。需要注意的是,魔力释放的过程不会真正改变区间内水晶球的能量。
值得一提的是,小 L 制造和融合的水晶球的原材料都是定制版的 OI 工厂水晶,所以这些水晶球有一个能量阈值 998244353 998244353 998244353。当水晶球中某种属性的能量值大于等于这个阈值时,能量值会自动对阈值取模,从而避免水晶球爆炸。
小 W 为小 L(唯一的)观众,围观了整个表演,并且收到了小 L 在表演中融合的每个水晶球。小 W 想知道,这些水晶球蕴涵的三种属性的能量值分别是多少。
输入格式
从标准输入读入数据。
我们将上述的 7 7 7 种魔法,从上到下依次标号为 1 1 1 ~ 7 7 7。
输入的第一行包含一个整数 n n n( 1 ≤ n ≤ 2.5 × 1 0 5 1\le n\le 2.5\times 10^5 1≤n≤2.5×105),表示水晶球个数。
接下来 n n n 行,每行空格隔开的 3 3 3 个整数,其中第 i i i 行的三个数依次表示 A i , B i , C i A_i,B_i,C_i Ai,Bi,Ci。
接下来一行包含一个整数 m m m( 1 ≤ m ≤ 2.5 × 1 0 5 1\le m\le2.5\times 10^5 1≤m≤2.5×105),表示施展魔法的次数。
接下来
m
m
m 行,每行
3
3
3 或
4
4
4 个数,格式为 opt l r (v)
。其中 opt
表示魔法的编号,
l
,
r
l,r
l,r 表示施展魔法的区间(保证有
l
≤
r
l\le r
l≤r)。特别地,如果施展
4
4
4 ~
6
6
6 号魔法(魔力增强),则还有一个整数
v
v
v,表示小 L 消耗的法力值。
输出格式
输出到标准输出。
对每个
7
7
7 号魔法(魔力释放),输出一行、空格隔开的
3
3
3 个整数 a b c
,分别表示此次融合得到的水晶球的水、火、土元素能量值。
样例 #1
样例输入 #1
2
2 3 3
6 6 6
4
7 1 2
1 1 2
4 1 2 3
7 1 2
样例输出 #1
8 9 9
23 9 9
提示
100 % 100\% 100% 的数据, n , m ≤ 2.5 × 1 0 5 , 0 ≤ A i , B i , C i , v < 998244353 n,m\le2.5\times 10^5,0\le A_i,B_i,C_i,v<998244353 n,m≤2.5×105,0≤Ai,Bi,Ci,v<998244353
- 10 % 10\% 10% 的数据, n × m ≤ 1 0 7 n\times m\le10^7 n×m≤107。
- 另外 10 % 10\% 10% 的数据,每次魔法的区间均为 [ 1 , n ] [1,n] [1,n]。
- 另外 10 % 10\% 10% 的数据,每次非询问魔法的影响区间均为 [ 1 , n ] [1,n] [1,n],所有修改在询问之前。
- 另外 10 % 10\% 10% 的数据, opt ∈ { 4 , 5 , 6 , 7 } \operatorname{opt}\in\{4,5,6,7\} opt∈{4,5,6,7}。
- 另外 15 % 15\% 15% 的数据, opt ∈ { 1 , 2 , 7 } \operatorname{opt}\in\{1,2,7\} opt∈{1,2,7}。
- 另外 15 % 15\% 15% 的数据, opt ∈ { 1 , 2 , 3 , 5 , 7 } \operatorname{opt}\in\{1,2,3,5,7\} opt∈{1,2,3,5,7}。
- 另外 15 % 15\% 15% 的数据, n , m ≤ 1 0 5 n,m\le 10^5 n,m≤105。
- 其他数据,无特殊约定。
样例解释
以下展示每次施展魔法后,两个水晶球内的能量:
(2, 3, 3) (6, 6, 6)
(5, 3, 3) (12, 6, 6)
(8, 3, 3) (15, 6, 6)
(8, 3, 3) (15, 6, 6)
题解
如同标签所述,这道题就三个考点,我们一个一个讲。
- 矩阵(自己到我之前的博客里面看吧)
☝这个是链接
这道题主要是要把矩阵定义成一个单独的结构体,线段树里要用。前六个不同的操作也都要单独赋值成一个矩阵。
4*4的矩阵主要表示每一个节点的A,B,C,Len。
-
操作1
-
操作2
-
操作3
-
操作4
-
操作5
-
操作6
要注意矩阵相乘的顺序!!!
- 线段树
线段树的每一个节点元素都是一个矩阵,包括他们的val和tag在内。(下面空着的就不要了)
-
val
-
tag(记得赋初值!!!)
- 常数优化
- 把矩阵乘法最里面一层展开
- 使用快读快写
- 减少取模的次数,用强制转换解决爆int的问题
代码实现
//洛谷 P7453 [THUSCH2017] 大魔法师
#pragma GCC optimize(3)
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
const int mo=998244353;
int n;
int m;
int opt;
int l,r,v;
int totr;
struct mat{
int num[4][4];
mat(){
memset(num,0,sizeof(num));
}
mat operator + (const mat x)const{
mat a;
for(int i=0;i<4;++i){
for(int j=0;j<4;++j){
a.num[i][j]=(1ll*num[i][j]+1ll*x.num[i][j])%mo;
}
}
return a;
}
mat operator * (const mat x)const{
mat a;
for(int i=0;i<4;++i){
for(int j=0;j<4;++j){
a.num[i][j]=(1ll*a.num[i][j]+1ll*num[0][j]*x.num[i][0])%mo;
a.num[i][j]=(1ll*a.num[i][j]+1ll*num[1][j]*x.num[i][1])%mo;
a.num[i][j]=(1ll*a.num[i][j]+1ll*num[2][j]*x.num[i][2])%mo;
a.num[i][j]=(1ll*a.num[i][j]+1ll*num[3][j]*x.num[i][3])%mo;
}
}
return a;
}
}a[250010],o[8],ans;
struct Node{
mat val;
mat tag;//延迟下放值
Node(){
tag.num[0][0]=tag.num[1][1]=tag.num[2][2]=tag.num[3][3]=1;
}
}rt[1000100];
void in(int &x){
int nt;
x=0;
while(!isdigit(nt=getchar()));
x=nt^'0';
while(isdigit(nt=getchar())){
x=(x<<3)+(x<<1)+(nt^'0');
}
}
inline mat calc(int x){//当前区间之和
return rt[x].tag*rt[x].val;
}
inline void push_down(int x){//tag延迟下放
rt[x<<1].tag=rt[x].tag*rt[x<<1].tag;//左子节点的tag加父节点的tag
rt[x<<1|1].tag=rt[x].tag*rt[x<<1|1].tag;//右子节点的tag加父节点的tag
rt[x].tag=mat();//父节点的tag清除
rt[x].tag.num[0][0]=rt[x].tag.num[1][1]=rt[x].tag.num[2][2]=rt[x].tag.num[3][3]=1;
}
inline int build(int x,int l,int r){//建树
if(l==r){//找到最下面的单个点
rt[x].val=a[r];
return x;
}
int mid=(l+r)/2;
build(x<<1,l,mid);//建左树
build(x<<1|1,mid+1,r);//建右树
rt[x].val=rt[x<<1].val+rt[x<<1|1].val;//本节点的左右子节点之和
return x;
}
inline void update(int x,int L,int R,int l,int r,mat v){//l~r区间值加v
if(l<=L&&R<=r){
rt[x].tag=v*rt[x].tag;
return ;
}
int mid=(L+R)/2;
push_down(x);
if(l<=mid){
update(x<<1,L,mid,l,r,v);
}
if(r>mid){
update(x<<1|1,mid+1,R,l,r,v);
}
rt[x].val=calc(x<<1)+calc(x<<1|1);//左右子节点之和
}
inline mat query(int x,int L,int R,int l,int r){
if(l<=L&&r>=R){
return calc(x);
}
int mid=(L+R)>>1;
mat tmp;
push_down(x);
rt[x].val=calc(x<<1)+calc(x<<1|1);
if(l<=mid){
tmp=tmp+query(x<<1,L,mid,l,r);
}
if(r>mid){
tmp=tmp+query(x<<1|1,mid+1,R,l,r);
}
return tmp;
}
int main(){
register int i;
in(n);
for(i=1;i<=n;++i){
in(a[i].num[0][0]),in(a[i].num[0][1]),in(a[i].num[0][2]);
a[i].num[0][3]=1;
}
build(1,1,n);
o[1].num[0][0]=o[1].num[1][0]=o[1].num[1][1]=o[1].num[2][2]=o[1].num[3][3]=1;
o[2].num[0][0]=o[2].num[1][1]=o[2].num[2][1]=o[2].num[2][2]=o[2].num[3][3]=1;
o[3].num[0][0]=o[3].num[1][1]=o[3].num[2][2]=o[3].num[0][2]=o[3].num[3][3]=1;
o[4].num[0][0]=o[4].num[1][1]=o[4].num[2][2]=o[4].num[3][3]=1;
o[5].num[0][0]=o[5].num[2][2]=o[5].num[3][3]=1;
o[6].num[0][0]=o[6].num[1][1]=o[6].num[3][3]=1;
in(m);
for(i=1;i<=m;++i){
in(opt);
in(l),in(r);
if(opt>=4&&opt<=6){
in(v);
v%=mo;
if(opt==4){
o[4].num[3][0]=v;
update(1,1,n,l,r,o[4]);
}
else if(opt==5){
o[5].num[1][1]=v;
update(1,1,n,l,r,o[5]);
}
else{
o[6].num[3][2]=v;
update(1,1,n,l,r,o[6]);
}
}
else{
if(opt<=3){
update(1,1,n,l,r,o[opt]);
}
else{
ans=query(1,1,n,l,r);
printf("%d %d %d\n",ans.num[0][0],ans.num[0][1],ans.num[0][2]);
}
}
}
return 0;
}