【题解】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ㄒ)/~~

浙公网安备 33010602011771号