【BZOJ4631】踩气球 题解(线段树)
----------------------
题目大意:给定一个长度为$n$的序列${a_i}$。现在有$m$个区间$[l_i,r_i]$和$q$个操作,每次选取一个$x$使得$a_x--$。问每一次操作后区间和为$0$的区间个数。
可以用主席树解决,但蒟蒻不会,蒟蒻只会写线段树QAQ。
对于每一个区间$[l_i,r_i]$,我们可以把它分解成线段树上$s$个区间,线段树每个结点维护一个向量数组,用来存包含它的区间。当某一个$a_x--$后,看线段树上区间和是否为0,如果是那么包含这个区间的区间$s--$。如果$s=0$那么$ans++$。
注意$update$向上回溯时也要判断一下是否为0,进行更新。
时间复杂度$O((m+q)\log n+n)$。
代码:
#include<bits/stdc++.h> using namespace std; int n,q,m,a[100005],ans; struct node { vector<int> v; int l,r,val; }tree[500005]; struct Node { int l,r,s; }t[100005]; inline int read() { int x=0,f=1;char ch=getchar(); while(!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();} while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();} return x*f; } inline void build(int index,int l,int r) { tree[index].l=l; tree[index].r=r; if (l==r) { tree[index].val=a[l]; return; } int mid=(l+r)>>1; build(index*2,l,mid); build(index*2+1,mid+1,r); tree[index].val=tree[index*2].val+tree[index*2+1].val; } inline void update(int index,int pos,int v) { if (tree[index].l==tree[index].r) { tree[index].val+=v; if (tree[index].val==0) { for (int i=0;i<tree[index].v.size();i++) { t[tree[index].v[i]].s--; if (t[tree[index].v[i]].s==0) ans++; } } return; } int mid=(tree[index].l+tree[index].r)>>1; if (pos<=mid) update(index*2,pos,v); else update(index*2+1,pos,v); tree[index].val=tree[index*2].val+tree[index*2+1].val; if (tree[index].val==0) { for (int i=0;i<tree[index].v.size();i++) { t[tree[index].v[i]].s--; if (t[tree[index].v[i]].s==0) ans++; } } } inline void split(int index,int l,int r,int id) { if (l<=tree[index].l&&tree[index].r<=r) { tree[index].v.push_back(id); t[id].s++; return; } int mid=(tree[index].l+tree[index].r)>>1; if (l<=mid) split(index*2,l,r,id); if (r>mid) split(index*2+1,l,r,id); } int main() { n=read(),m=read(); for (int i=1;i<=n;i++) a[i]=read(); build(1,1,n); for (int i=1;i<=m;i++) t[i].l=read(),t[i].r=read(); for (int i=1;i<=m;i++) split(1,t[i].l,t[i].r,i); q=read(); while(q--) { int x=read(); x=(x+ans-1)%n+1; update(1,x,-1); printf("%d\n",ans); } return 0; }