题解
考虑每一次踩气球对答案有贡献,当且仅当踩爆的是盒子中最后一个气球,这时设当这个气球所在的盒子是$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 ",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; }