把博客园图标替换成自己的图标
把博客园图标替换成自己的图标end

【洛谷3710】方方方的数据结构(四分树)

点此看题面

  • 给定一个长度为\(n\)的序列,要求支持四种操作:区间加;区间乘;单点询问;撤销一个操作(即除去这个操作,并保持其他操作间相对顺序不变)。
  • \(n,q\le1.5\times10^5\),数据随机

时间轴+四分树

这种撤销操作的问题一看就非常恐怖,如果用常规的方法去做显然非常困难。

因此我们考虑时间轴,根据时间轴序列下标这两维建立一个二维坐标系。

那么一个操作的影响范围就可以表示为一个二维区间。

容易想到二维线段树,但会被卡空间。由于这道题保证了数据随机,我们可以使用经常遭受迫害的四分树。

然后这道题实际上有一个非常好的技巧,就是由于询问是单点的,所以有用的叶节点实际上不到\(q\)个。

因此我们预先建树,只建出有用的节点,修改时碰上无用节点直接退出。

这样一棵树的节点个数只有\(O(nlogn)\)个,内存过关了。

代码:\(O(n\sqrt 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('\n');}
}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;
}
posted @ 2021-05-04 17:56  TheLostWeak  阅读(82)  评论(0编辑  收藏  举报