考虑一个离线做法
暂时先不去考虑这个余3的限制,假设是求排序后前一半数的和,那么很明显,对于每一个add和del,将区间进行加,减修改,最后求一个类似query(1,1,n/2)的值即可。
那么现在是求余3的和,也离线来做吧。
对于add(x),就找到x的位置pos,将[pos,pos]区间累加上x,对于del(x),也是找到pos,将[pos,pos]区间累加上x。那么怎么求哪些数排完序后是余3的呢?
我们把原来的sum[k]表示线段树第k个区间的区间和,变为sum[k][0],sum[k][1],sum[k][2],sum[k][3],sum[k][4],表示线段树第k个区间中,下标余0,余1,余2,余3,余4的区间和。
那么如何实现维护呢?并不是简单的sum[k][i]=sum[k<<1][i]+sum[k<<1|1][i]就可以的了。
正确的公式应该是sum[k][i]=sum[k<<1][i]+sum[k<<1|1][i-now],now是k<<1区间内的数的个数,所以我们再维护一个add[k]表示线段树的第k个区间的区间内有多少个数,这样一来,设t=add[k<<1],那么sum[k][i]=sum[k<<1][i]+sum[k<<1|1][((i-add[k<<1)%5+5)%5]。
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int N=1e5+5;
int n,tot;
int b[N],add[N<<2];
ll sum[N<<2][5];
struct number{int x,f;}num[N];
char str[10];
void change(int k,int l,int r,int v1,int v2,int pos)
{
if (l==r)
{
sum[k][1]+=(ll)v1;
add[k]+=v2;
return;
}
int mid=l+r>>1;
if (pos<=mid) change(k<<1,l,mid,v1,v2,pos);
else change(k<<1|1,mid+1,r,v1,v2,pos);
add[k]=add[k<<1]+add[k<<1|1];
for (register int i=0; i<=4; ++i)
{
int now=((i-add[k<<1])%5+5)%5;
sum[k][i]=sum[k<<1][i]+sum[k<<1|1][now];
}
}
int main(){
scanf("%d",&n);
for (register int i=1; i<=n; ++i)
{
scanf("%s",str+1);
if (str[1]=='a')
{
scanf("%d",&num[i].x); num[i].f=1;
b[++tot]=num[i].x;
}
else if (str[1]=='d')
{
scanf("%d",&num[i].x); num[i].f=-1;
}
else
{
num[i].f=0;
}
}
sort(b+1,b+tot+1);
tot=unique(b+1,b+tot+1)-b-1;
for (register int i=1; i<=n; ++i)
{
if (!num[i].f) printf("%lld\n",sum[1][3]);
else
{
int v1=num[i].x*num[i].f;
int v2=num[i].f;
int pos=lower_bound(b+1,b+tot+1,num[i].x)-b;
change(1,1,n,v1,v2,pos);
}
}
return 0;
}