题意:
有一个序列和一个区间集合,每次将序列中的一个数-1,求此时集合里有多少个区间和为0。序列大小≤100000,区间数≤100000,操作数≤100000。
题解:
此题解法其实并不难,对序列建线段树,用线段树每个节点维护区间和及覆盖该区间的集合内的区间的链表,同时记录每个集合内区间被分割为多少个区间。操作时就把查询经过的节点的区间和-1,如果为0则将覆盖该节点的区间的分割数-1,当分割数为0就让答案++。问题是复杂度,总是要遍历链表不会很慢吗?后来仔细想了一下,每次向线段树挂区间时最多挂logn个节点,共影响到mlogn个节点,因此遍历链表的总节点数为mlogn,且当一个节点区间和变为0遍历链表后就永远不会再遍历,因此总复杂度大致是O(mlogn)。
代码:
#include <cstdio>
#include <cstring>
#include <algorithm>
#define inc(i,j,k) for(int i=j;i<=k;i++)
#define maxn 100100
using namespace std; struct nd{int v,n;}; nd nds[maxn*]; int v[maxn*],tot[maxn],g[maxn*],n,m,a[maxn],q,ans,ndss;
inline int read(){
char ch=getchar(); int f=,x=;
while(ch<''||ch>''){if(ch=='-')f=-; ch=getchar();}
while(ch>=''&&ch<='')x=x*+ch-'',ch=getchar();
return f*x;
}
void ins(int num,int node){
nds[++ndss]=(nd){num,g[node]}; g[node]=ndss;
}
void update(int x){
for(int i=g[x];i;i=nds[i].n){tot[nds[i].v]--; if(!tot[nds[i].v])ans++;}
}
void build(int x,int l,int r){
if(l==r){v[x]=a[l]; return;}; int mid=l+r>>;
build(x<<,l,mid); build(x<<|,mid+,r); v[x]=v[x<<]+v[x<<|];
}
void insert(int x,int l,int r,int ql,int qr,int num){
if(ql<=l&&r<=qr){ins(num,x); tot[num]++; return;} int mid=l+r>>;
if(ql<=mid)insert(x<<,l,mid,ql,qr,num); if(mid<qr)insert(x<<|,mid+,r,ql,qr,num);
}
void change(int x,int l,int r,int q){
v[x]--; if(!v[x])update(x); if(l==r)return; int mid=l+r>>;
if(q<=mid)change(x<<,l,mid,q);else change(x<<|,mid+,r,q);
}
int main(){
//freopen("in.txt","r",stdin);
n=read(); m=read(); inc(i,,n)a[i]=read(); build(,,n);
inc(i,,m){int l=read(),r=read(); insert(,,n,l,r,i);} q=read();
inc(i,,q){int x=(read()+ans-)%n+; change(,,n,x); printf("%d\n",ans);}
return ;
}
20160723