【题解】CF1004F Sonya and Bitwise OR

0:单次操作

首先考虑单次操作

对于一次询问 \([L,R]\),我们可以分治处理

对于每一层处理跨 \(mid\) 的贡献,发现可以维护前缀或双指针加速

单次询问复杂度 \(O(len \times log(len))\)

1:重复统计加速

\(m\) 次询问,复杂度 \(O(m \times n \times log(n))\)

发现一个区间会在多次询问中重复使用,可以建线段树直接维护信息

那么单次修改 \(O(n \times log(n))\),单次查询由于需要暴力统计跨块贡献 \(O(n \times log(n))\)

2:log分块加速

一个区间值不同的前缀或个数为 \(log(V)\) 个,我们只需要维护它们的位置和值即可

这样的话复杂度优化到 单次修改 \(O(log^2(n))\),单次查询 \(O(log^2(n))\)

3:代码

超多细节QWQ

#include<bits/stdc++.h>
#define int long long
#define Pair pair<int,int>
#define tp first
#define val second
using namespace std;
const int N=1e5+10;int n,m,X;
int a[N];
struct node{
	int l,r;
	vector<Pair> pre,suf;//<- , ->
	int ans;
	#define l(q) tree[q].l
	#define r(q) tree[q].r
	#define pre(q) tree[q].pre
	#define suf(q) tree[q].suf
	#define ans(q) tree[q].ans
	void show(){
		cout<<"-->"<<l<<" "<<r<<" "<<ans<<"\n";
		for(auto [u,v]:pre) cout<<u<<" "<<v<<"   ";cout<<"\n";
		for(auto [u,v]:suf) cout<<u<<" "<<v<<"   ";cout<<"\n";
	}
}tree[N<<2];
void push_up(int q){
	ans(q)=ans(q<<1)+ans(q<<1|1);
	for(int i=0,j=suf(q<<1|1).size()-1;i<pre(q<<1).size();i++){
		int k=((i==pre(q<<1).size()-1)?pre(q<<1)[i].tp-l(q)+1:pre(q<<1)[i].tp-pre(q<<1)[i+1].tp);
		while(j>=0&&(pre(q<<1)[i].val|suf(q<<1|1)[j].val)>=X) j--;
		if(j!=suf(q<<1|1).size()-1) ans(q)+=k*(r(q)-suf(q<<1|1)[j+1].tp+1);
	}
	
	
	pre(q)=pre(q<<1|1);
	for(int i=0;i<pre(q<<1).size();i++){
		if((pre(q).back().val|a[pre(q<<1)[i].tp])!=pre(q).back().val){
			pre(q).push_back({pre(q<<1)[i].tp,pre(q).back().val|a[pre(q<<1)[i].tp]});
		}
	}
	
	
	suf(q)=suf(q<<1);
	for(int i=0;i<suf(q<<1|1).size();i++){
		if((suf(q).back().val|a[suf(q<<1|1)[i].tp])!=suf(q).back().val){
			suf(q).push_back({suf(q<<1|1)[i].tp,suf(q).back().val|a[suf(q<<1|1)[i].tp]});
		}
	}
	
	//tree[q].show();
}
void build(int q,int l,int r){
	l(q)=l;r(q)=r;
	if(l==r){
		ans(q)=(a[l]>=X);
		pre(q).push_back({l,a[l]});
		suf(q).push_back({l,a[l]});
		return ;
	}
	int mid=l+r>>1;
	build(q<<1,l,mid);
	build(q<<1|1,mid+1,r);
	push_up(q);
}
void update(int q,int tp,int d){
	int l=l(q),r=r(q);
	if(l==r){
		pre(q)[0]={tp,d};
		suf(q)[0]={tp,d};
		ans(q)=(d>=X);
		a[l]=d;
		return ;
	}
	int mid=l+r>>1;
	if(tp<=mid) update(q<<1,tp,d);
	else update(q<<1|1,tp,d);
	push_up(q);
}
int query(int q,int L,int R){
	int l=l(q),r=r(q);
	if(L<=l&&r<=R){
		return ans(q);
	}
	int mid=l+r>>1;
	if(L<=mid&&mid<R){
		int as=query(q<<1,L,R)+query(q<<1|1,L,R);
		
		//cout<<as<<"\n";
		int j=suf(q<<1|1).size()-1;
		
		int LL=max(L,l),RR=min(R,r);
		
		while(j>=0&&suf(q<<1|1)[j].tp>RR) j--;
		int zr=j;
		
		
		for(int i=0;i<pre(q<<1).size()&&pre(q<<1)[i].tp>=LL;i++){
			int k=((i==pre(q<<1).size()-1||pre(q<<1)[i+1].tp<LL)?pre(q<<1)[i].tp-LL+1:pre(q<<1)[i].tp-pre(q<<1)[i+1].tp);
			while(j>=0&&(pre(q<<1)[i].val|suf(q<<1|1)[j].val)>=X) j--;
			if(j!=zr) as+=k*(RR-suf(q<<1|1)[j+1].tp+1);
			//cout<<pre(q<<1)[i].tp-LL+1<<"asdfasdf\n";
		}
		//cout<<"->"<<l<<" "<<r<<" "<<as<<" "<<LL<<" "<<RR<<"\n";
		return as;
	}
	else if(L<=mid) return query(q<<1,L,R);
	else if(mid<R) return query(q<<1|1,L,R);
}
signed main(){
	ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
	cin>>n>>m>>X;
	for(int i=1;i<=n;i++) cin>>a[i];
	build(1,1,n);
	while(m--){
		int op,x,y;cin>>op>>x>>y;
		if(op==1) update(1,x,y);
		else cout<<query(1,x,y)<<"\n";
	}
	return 0;
}

4:反思

*4.1:区间前缀或性质

一个区间的前缀或的值域最大数量为 \(log(V)\)\(V\) 为原数组最大值

4.2:渐进分析

可以先局部分析,然后拓展到整体分析

*4.3:分治思想

形如 \(L<=l<=r<=R\) 这类询问的问题,可以考虑分治

*4.4:从分治到线段树

如果出现分治区间重复使用,可以进化至线段树,提前维护信息

4.5

写代码一定要注意细节!!不然会像我一样代码调好几天/(ㄒoㄒ)/~~

posted @ 2026-07-12 15:11  Aistyr  阅读(3)  评论(0)    收藏  举报