- 给定一个长度为(n)的序列,要求支持四种操作:区间加;区间乘;单点询问;撤销一个操作(即除去这个操作,并保持其他操作间相对顺序不变)。
- (n,qle1.5 imes10^5),数据随机
时间轴+四分树
这种撤销操作的问题一看就非常恐怖,如果用常规的方法去做显然非常困难。
因此我们考虑时间轴,根据时间轴和序列下标这两维建立一个二维坐标系。
那么一个操作的影响范围就可以表示为一个二维区间。
容易想到二维线段树,但会被卡空间。由于这道题保证了数据随机,我们可以使用经常遭受迫害的四分树。
然后这道题实际上有一个非常好的技巧,就是由于询问是单点的,所以有用的叶节点实际上不到(q)个。
因此我们预先建树,只建出有用的节点,修改时碰上无用节点直接退出。
这样一棵树的节点个数只有(O(nlogn))个,内存过关了。
代码:(O(nsqrt n))
#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 150000
#define X 998244353
using namespace std;
int n,Qt;struct Q {int op,x,l,r,ed;}q[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 writeln(Ty x) {W(OS[++OT]=x%10+48,x/=10);W(OT) pc(OS[OT--]);pc('
');}
}using namespace FastIO;
int Rt;class QuarterTree
{
private:
#define PT CI lx=1,CI rx=Qt,CI ly=1,CI ry=n,int& rt=Rt
#define S0 lx,mx,ly,my,O[rt].S[0]
#define S1 lx,mx,my+1,ry,O[rt].S[1]
#define S2 mx+1,rx,ly,my,O[rt].S[2]
#define S3 mx+1,rx,my+1,ry,O[rt].S[3]
#define TA(x,v) (O[x].F=(O[x].F+v)%X)
#define TM(x,v) (O[x].F=1LL*O[x].F*v%X,O[x].G=1LL*O[x].G*v%X)
int Nt;struct node {int F,G,S[4];}O[N*30];
I void PD(CI rt)//下传标记
{
if(O[rt].G^1) {for(RI i=0;i^4;++i) O[rt].S[i]&&TM(O[rt].S[i],O[rt].G);O[rt].G=1;}//先传乘法
if(O[rt].F) {for(RI i=0;i^4;++i) O[rt].S[i]&&TA(O[rt].S[i],O[rt].F);O[rt].F=0;}//再传加法
}
public:
I void Bd(CI x,CI y,PT)//建出有用节点
{
if(!rt&&(O[rt=++Nt].G=1),lx==rx&&ly==ry) return;RI mx=lx+rx>>1,my=ly+ry>>1;
x<=mx?(y<=my?Bd(x,y,S0):Bd(x,y,S1)):(y<=my?Bd(x,y,S2):Bd(x,y,S3));
}
I void A(CI Lx,CI Rx,CI Ly,CI Ry,CI v,PT)//区间加法
{
if(!rt) return;if(Lx<=lx&&rx<=Rx&&Ly<=ly&&ry<=Ry) return (void)TA(rt,v);
PD(rt);RI mx=lx+rx>>1,my=ly+ry>>1;
Lx<=mx&&(Ly<=my&&(A(Lx,Rx,Ly,Ry,v,S0),0),Ry>my&&(A(Lx,Rx,Ly,Ry,v,S1),0));
Rx>mx&&(Ly<=my&&(A(Lx,Rx,Ly,Ry,v,S2),0),Ry>my&&(A(Lx,Rx,Ly,Ry,v,S3),0));
}
I void M(CI Lx,CI Rx,CI Ly,CI Ry,CI v,PT)//区间乘法
{
if(!rt) return;if(Lx<=lx&&rx<=Rx&&Ly<=ly&&ry<=Ry) return (void)TM(rt,v);
PD(rt);RI mx=lx+rx>>1,my=ly+ry>>1;
Lx<=mx&&(Ly<=my&&(M(Lx,Rx,Ly,Ry,v,S0),0),Ry>my&&(M(Lx,Rx,Ly,Ry,v,S1),0));
Rx>mx&&(Ly<=my&&(M(Lx,Rx,Ly,Ry,v,S2),0),Ry>my&&(M(Lx,Rx,Ly,Ry,v,S3),0));
}
I int Q(CI x,CI y,PT)//单点询问
{
if(lx==rx&&ly==ry) return O[rt].F;PD(rt);RI mx=lx+rx>>1,my=ly+ry>>1;
return x<=mx?(y<=my?Q(x,y,S0):Q(x,y,S1)):(y<=my?Q(x,y,S2):Q(x,y,S3));
}
}T;
int main()
{
RI i;for(read(n,Qt),i=1;i<=Qt;++i) switch(read(q[i].op),q[i].op)
{
case 1:case 2:read(q[i].l,q[i].r,q[i].x),q[i].ed=Qt;break;//初始化时间轴上右端点为Qt
case 3:read(q[i].x),T.Bd(i,q[i].x);break;case 4:read(q[i].x),q[q[i].x].ed=i;break;//撤销看成设定对应操作时间轴上的右端点
}
for(i=1;i<=Qt;++i) switch(q[i].op)
{
case 1:T.A(i,q[i].ed,q[i].l,q[i].r,q[i].x);break;case 2:T.M(i,q[i].ed,q[i].l,q[i].r,q[i].x);break;//二维区间修改
case 3:writeln(T.Q(i,q[i].x));break;//单点询问
}return clear(),0;
}