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

【UOJ671】【UNR #5】诡异操作(线段树+位运算)

题目链接

  • 给定一个长度为 \(n\) 的序列 \(a_{1\sim n}\)
  • \(q\) 次操作,分为三种:区间除以 \(v\) 并向下取整;区间向 \(v\) 按位与;询问区间和。
  • \(1\le n\le3\times10^5\)\(1\le q\le2\times10^5\)\(0\le a_i,v < 2^{128}\)

常规思路

首先容易发现两种操作都只会使 \(a_i\) 越来越小,所以对于任意一个 \(a_i\),忽略掉 \(v=1\) 的操作后,在 \(O(\log a_i)\) 次除法操作后便会变成 \(0\)。所以,对于区间除法,可以直接暴力修改。

而对于区间按位与操作,只要分别维护每一位,则 \(v\)\(1\) 的位不变,\(v\)\(0\) 的位清零。

这样的复杂度是 \(O((n\log V+q\log n)\log V)\),但由于 \(\log V\) 非常大,会 TLE。

奇怪的优化操作

好像之前也写过区间按位与操作的题目,然而并不知道这个优化。

常规思路是对于每一位维护数的个数,显然个数不可能超过 \(n\)。因此,把个数看作二进制数,则相当于维护了一个 \(\log V\times \log n\) 的表格。

考虑将表格翻转,变成一个 \(\log n\times\log V\) 的表格。也就是用 \(\log n\) 个变量,第 \(i\) 个变量压位维护每一位答案在二进制下第 \(i\) 位的值。

发现这样一来就有很多好处:首先,对于单独一个数需要维护的就是它自身,不需要再花 \(O(\log V)\) 的时间做二进制拆解;其次,现在只需维护 \(\log n\) 个变量,而不是 \(\log V\) 个变量,维护的时间复杂度也就变成了 \(\log n\),询问答案的时候也只要扫一遍求出 \(V_i\times2^i\) 之和即可。

所以现在的时间复杂度就被优化成了 \(O(n\log V+q\log^2n)\)

余下的一个问题是如何上传信息,也就是实现 \(\log V\) 个压在一起的二进制数的加法。

判断进位只要判断两个相加的数以及上次进位的数是否有至少两个 \(1\)(两两按位与再按位或),而这位剩余值可以直接用不进位加法(异或)计算。

写的时候注意常数。像我一开始代码写得太丑,结果卡了一个上午才卡过去。

代码:\(O(n\log V+q\log^2n)\)

#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Rg register
#define RI Rg int
#define Cn const
#define CI Cn int&
#define I inline
#define W while
#define N 300000
#define SZ 1500000
#define INT __uint128_t
using namespace std;
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...);}
	I void readINT(INT& x) {x=0;W(isspace(oc=tc()));W((x<<=4)|=isdigit(oc)?(oc&15):oc-87,!isspace(oc=tc()));}
	I void writeINT(INT x) {W(OS[++OT]=(x&15)<=9?48|(x&15):87+(x&15),x>>=4);W(OT) pc(OS[OT--]);pc('\n');}
}using namespace FastIO;
int n;INT a[N+5];
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 PD(rt) (~F[rt]&&(T(rt<<1,F[rt]),T(rt<<1|1,F[rt]),F[rt]=-1))
		int G[N<<2];INT pV[SZ],*nw,*V[N<<2],F[N<<2],E[N<<2];
		I void PU(CI x)//上传信息
		{
			E[x]=E[x<<1]|E[x<<1|1],G[x]=max(G[x<<1],G[x<<1|1]);INT *A=V[x<<1],*B=V[x<<1|1],C=0;RI i;
			for(i=0;i<=min(G[x<<1],G[x<<1|1]);++i,++A,++B) V[x][i]=*A^*B^C,C=(*A&*B)|((*A|*B)&C);//异或计算剩余值,按位与计算进位值
			for(;i<=G[x<<1];++i,++A) V[x][i]=*A^C,C&=*A;for(;i<=G[x<<1|1];++i,++B) V[x][i]=*B^C,C&=*B;
			C&&(V[x][++G[x]]=C);//如果最后还有进位,将位数加1
		}
		I void T(CI x,INT v) {if((E[x]&v)==E[x]) return;E[x]&=v,F[x]&=v;for(RI i=0;i<=G[x];++i) V[x][i]&=v;W(~G[x]&&!V[x][G[x]]) --G[x];}//将x子树内按位与v
		I INT Calc(CI x) {INT t=0;for(RI i=G[x];~i;--i) (t<<=1)+=V[x][i];return t;}//计算值
	public:
		I SegmentTree() {nw=pV;}
		I int Bd(PT)//建树,同时分配内存
		{
			if(F[rt]=-1,l==r) return V[rt]=nw,++nw,G[rt]=(E[rt]=V[rt][0]=a[l])?0:-1,0;
			RI mid=l+r>>1,d=max(Bd(LT),Bd(RT))+1;return V[rt]=nw,nw+=d+1,PU(rt),d;
		}
		I void Div(CI L,CI R,INT v,PT)//区间除法
		{
			if(!~G[rt]) return;if(l==r) return (void)(G[rt]=(E[rt]=(V[rt][0]/=v))?0:-1);
			RI mid=l+r>>1;PD(rt),L<=mid&&(Div(L,R,v,LT),0),R>mid&&(Div(L,R,v,RT),0),PU(rt);
		}
		I void And(CI L,CI R,INT v,PT)//区间按位与
		{
			if(!~G[rt]||(E[rt]&v)==E[rt]) return;if(L<=l&&r<=R) return T(rt,v);RI mid=l+r>>1;PD(rt),L<=mid&&(And(L,R,v,LT),0),R>mid&&(And(L,R,v,RT),0),PU(rt);
		}
		I INT Q(CI L,CI R,PT)//区间求和
		{
			if(!~G[rt]) return 0;if(L<=l&&r<=R) return Calc(rt);RI mid=l+r>>1;PD(rt);return (L<=mid?Q(L,R,LT):0)+(R>mid?Q(L,R,RT):0);
		}
}S;
int main()
{
	RI Qt,i,op,x,y;INT v;for(read(n,Qt),i=1;i<=n;++i) readINT(a[i]);S.Bd();
	W(Qt--) read(op,x,y),op^3?(readINT(v),op==1?(void)(v^1&&(S.Div(x,y,v),0)):S.And(x,y,v)):writeINT(S.Q(x,y));return clear(),0;
}
posted @ 2022-01-26 11:49  TheLostWeak  阅读(154)  评论(0编辑  收藏  举报