UOJ671 【UNR #5】诡异操作

Description

区间加,区间去 AND ,区间求和

n3×105,Q2×105,ai,v2128

Solution

特判除数 v=1 的情况,那么暴力进行除法操作复杂度就是 Θ(nlogV) ,可以接受

常规思路维护 logV 个二进制数表示区间里面 ai 在第 1logV 位中为 1 的数量是多少,但是复杂度 Θ(nlog2V+qlogVlogn) ,不能通过

注意到在常规思路中线段树每个节点本质上维护的是一个 logV×logn 的数表

第二维大小是 logn 的原因是数字数量不会超过 n,更严格的,应当为 loglen

那么可以将这个数表进行翻转!现在变成维护 logn 个长度为 logV 的二进制数

区间求和就是 vali×2i ,除法计算时直接使用 val0 就是真实的节点权值,甚至不用重构叶子处的数表,取 AND 就是对所有 vali 进行操作

剩余的唯一问题就是 push_up 操作中数表本身的维护,注意到每位的权值是独立的,那么维护第 i1 位两个子区间向量的进位情况 carry(压缩成一个长度为 1280/1 串)

不难发现 vali 就是 vallsonvalrsoncarry ,而在两者加法中完成进位的不会再进一次,或起来即可

时间复杂度降至 Θ(nlogV+qlog2n) ,需要加入的常数优化有二:

  • 数表的大小是 loglen ,所以需要使用给每个节点在内存池中分配空间而不是开到 N×logn ,注意总空间是 2ilogn2ini2in

  • 区间全零后在区间取 AND 和区间求和也可以进行剪枝

Code

const int N=3e5+10;
int n,Q,num[N<<2];
u128 Memory_pool[N*5+1000],*indic;
u128 a[N],tag[N<<2],*val[N<<2];
bool zero[N<<2];
#define ls p<<1
#define rs p<<1|1
#define lson p<<1,l,mid
#define rson p<<1|1,mid+1,r
inline void push_up(int p){
	u128 carry=0;
	for(int i=0;i<=num[p];++i){
		u128 x=i<=num[ls]?val[ls][i]:0;
		u128 y=i<=num[rs]?val[rs][i]:0;
		val[p][i]=x^y^carry;
		carry=((x^y)&carry)|(x&y);
	}
	zero[p]=zero[ls]&zero[rs];
	return ;
}
inline void push_tag(int p,u128 v){
	if(zero[p]) return ;
	tag[p]&=v;
	rep(i,0,num[p]) val[p][i]&=v;
	return ;
}
inline void push_down(int p){
	if(~tag[p]){
		push_tag(ls,tag[p]);
		push_tag(rs,tag[p]);
		tag[p]=-1;
	} return ;
}
inline void build(int p,int l,int r){
	tag[p]=-1; num[p]=31-__builtin_clz(r-l+1);
	val[p]=indic; indic+=num[p]+1;
	if(l==r){
		if(!(val[p][0]=a[l])) zero[p]=1;
		return ;
	}
	int mid=(l+r)>>1;
	build(p<<1,l,mid); build(p<<1|1,mid+1,r);
	return push_up(p);
}
inline u128 Query(int st,int ed,int p=1,int l=1,int r=n){
	if(zero[p]) return 0;
	if(st<=l&&r<=ed){
		u128 res=0;
		rep(i,0,num[p]) res+=val[p][i]<<i;
		return res;
	}
	int mid=(l+r)>>1; push_down(p);
	u128 res=0;
	if(st<=mid) res+=Query(st,ed,lson);
	if(ed>mid) res+=Query(st,ed,rson);
	return res;
}
inline void Div(int st,int ed,u128 v,int p=1,int l=1,int r=n){
	if(zero[p]) return ;
	if(l==r){
		zero[p]=!(val[p][0]/=v);
		return ;
	}
	int mid=(l+r)>>1; push_down(p);
	if(st<=mid) Div(st,ed,v,lson);
	if(ed>mid) Div(st,ed,v,rson);
	return push_up(p);
}
inline void And(int st,int ed,u128 v,int p=1,int l=1,int r=n){
	if(zero[p]) return ;
	if(st<=l&&r<=ed) return push_tag(p,v);
	int mid=(l+r)>>1; push_down(p);
	if(st<=mid) And(st,ed,v,lson);
	if(ed>mid) And(st,ed,v,rson);
	return push_up(p);
}
#undef ls
#undef rs
#undef lson
#undef rson
signed main(){
	n=read(); Q=read();	
	for(int i=1;i<=n;++i) a[i]=read_16();
	indic=Memory_pool;
	build(1,1,n);
	while(Q--){
		int opt=read(),l=read(),r=read();
		if(opt==1){
			u128 v=read_16();
			if(v!=1) Div(l,r,v);	
		}
		if(opt==2){
			u128 v=read_16();
			And(l,r,v);	
		}
		if(opt==3) output(Query(l,r)),putchar('\n');
	}
	return 0;
}
posted @   没学完四大礼包不改名  阅读(135)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
点击右上角即可分享
微信分享提示