题解
考虑每一次踩气球对答案有贡献,当且仅当踩爆的是盒子中最后一个气球,这时设当这个气球所在的盒子是$x$,多出来的区间就是,左端点在与$x$左侧相邻的一段空盒子中,且右端点在右侧相邻的一段空盒子中的这样的区间。
由于本题强制在线,我们考虑用并查集$+$线段树$+$启发式合并维护,把一段连续的空盒子看作一个连通块,用并查集维护,并查集上的祖先是连通块的右端点同时记录左端点。初始时若有熊孩子的区间是$[L,R]$,则类似主席树在以$R$为根的线段树中插入一个$L$,统计一个段连续空盒子$[u,v]$的答案只需统计这个连通块所对应的线段树中大于等于$L$的数的数量即可。由于每一次修改最多改变三个连通块的信息,因此我们直接统计每一次操作对答案的贡献即可。
#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
#include<cmath>
#define LL long long
#define M 400020
#define mid ((l+r)>>1)
using namespace std;
int read(){
int nm=0,fh=1; char cw=getchar();
for(;!isdigit(cw);cw=getchar()) if(cw=='-') fh=-fh;
for(;isdigit(cw);cw=getchar()) nm=nm*10+(cw-'0');
return nm*fh;
}
int n,m,T,f[M],minn[M],sum[M*15],L[M*15],R[M*15];
int u,v,rem[M],cnt,rt[M],ans;
int fd(int x){return f[x]==x?x:(f[x]=fd(f[x]));}
void ins(int &x,int pre,int l,int r,int pos){
x=++cnt,sum[x]=sum[pre]+1,L[x]=L[pre],R[x]=R[pre];
if(l==r) return;
if(pos<=mid) ins(L[x],L[pre],l,mid,pos);
else ins(R[x],R[pre],mid+1,r,pos);
}
int merge(int x,int y){
if(!x||!y) return x+y; sum[x]=sum[x]+sum[y];
L[x]=merge(L[x],L[y]),R[x]=merge(R[x],R[y]);
return x;
}
int query(int x,int l,int r,int pos){
if(l>=pos) return sum[x];
if(r<pos||!sum[x]) return 0;
return query(L[x],l,mid,pos)+query(R[x],mid+1,r,pos);
}
int getans(int x){
if(rem[x=fd(x)]) return 0;
return query(rt[x],1,n,minn[x]);
}
int main(){
n=read(),T=read(),ans=0;
for(int i=1;i<=n;i++) rem[i]=read(),f[i]=minn[i]=i;
while(T--) u=read(),v=read(),ins(rt[v],rt[v],1,n,u);
for(T=read();T;T--,printf("%d\n",ans)){
u=read(),u=(u+ans-1)%n+1,rem[u]--;
if(!rem[u]){
if(rem[u-1]==0&&u>1) minn[u]=minn[u-1],ans-=getans(u-1),f[u-1]=u,rt[u]=merge(rt[u],rt[u-1]);
if(rem[u+1]==0&&u<n) ans-=getans(fd(u+1)),minn[f[u]=fd(u+1)]=minn[u],rt[fd(u)]=merge(rt[fd(u+1)],rt[u]);
ans+=getans(fd(u));
}
}
return 0;
}