已知n(1e5)个数,有q(1e5)个操作,操作1修改数组的某一数,操作2,l,r,x问有多少个在l到r子区间里x的个数为奇数个。
因为询问是对数x来找子区间,而数的个数最多2e5,因此可以动态的开点维护这么多线段树,这样空间复杂度是nlogn,不会超,更新线段树要将数为x的线段树对应的位置进行01修改,为了求子区间个数,线段树维护什么呢?区间长度len,区间和sum,区间答案(题目所求)ans,必取左端点的区间答案ansl,必取右端点的区间答案ansr,更新ans=ls(ans)+rs(ans)+ls(ansr)*(rs(len)-rs(ansl))+(ls(len)-ls(ansr))*rs(ansl);其它的更新就更简单了,更新的时候要注意区间长度,因为是动态开点,有的点可能没开到,但是区间长度是固定的。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=2e5+9;
int n,q,a[N];
unordered_map<int,int>mp;int nm;
struct T{
int len,sum;
ll ans,ansl,ansr;
}v[N<<5];
int tot,rt[N],L[N<<5],R[N<<5];
T operator+(T t1,T t2){
T t;
t.len=t1.len+t2.len;
t.sum=t1.sum+t2.sum;
t.ansl=t1.ansl;
if(t1.sum%2)t.ansl+=t2.len-t2.ansl;else t.ansl+=t2.ansl;
t.ansr=t2.ansr;
if(t2.sum%2)t.ansr+=t1.len-t1.ansr;else t.ansr+=t1.ansr;
t.ans=t1.ans+t2.ans;
t.ans+=t1.ansr*(t2.len-t2.ansl);
t.ans+=(t1.len-t1.ansr)*t2.ansl;
return t;
}
void update(int &now,int l,int r,int qx,int qy){
if(!now)now=++tot;
if(l==r){v[now].sum=v[now].ans=v[now].ansl=v[now].ansr=qy;return;}
int mid=(l+r)>>1;
if(qx<=mid)update(L[now],l,mid,qx,qy);
else update(R[now],mid+1,r,qx,qy);
v[L[now]].len=mid-l+1,v[R[now]].len=r-mid;
v[now]=v[L[now]]+v[R[now]];
}
T query(int now,int l,int r,int ql,int qr){
if(now==0)return (T){0,0,0,0,0};
if(ql<=l&&r<=qr)return v[now];
else{
int mid=(l+r)>>1;
T t=(T){0,0,0,0,0},tt;
if(ql<=mid)tt=query(L[now],l,mid,ql,qr),tt.len=mid-max(ql,l)+1,t=t+tt;
if(qr>mid)tt=query(R[now],mid+1,r,ql,qr),tt.len=min(qr,r)-mid,t=t+tt;
return t;
}
}
int main(){
// freopen("tt.in","r",stdin),freopen("tt.out","w",stdout);
ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
cin>>n;
for(int i=1;i<=n;i++){
cin>>a[i];
if(!mp[a[i]])mp[a[i]]=++nm;
update(rt[mp[a[i]]],1,n,i,1);
}
cin>>q;
while(q--){
int opt,x,v,l,r;
cin>>opt;
if(opt==1){
cin>>x>>v;
update(rt[mp[a[x]]],1,n,x,0),a[x]=v;
if(!mp[v])mp[v]=++nm;
update(rt[mp[a[x]]],1,n,x,1);
}else{
cin>>l>>r>>x;
cout<<query(rt[mp[x]],1,n,l,r).ans<<endl;
}
}
return 0;
}