BZOJ4631 踩气球

题解

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

  

原文地址:https://www.cnblogs.com/OYJason/p/9620891.html