最近做了不少树状数组的题目,不得不感叹,这真是一个优美的数据结构。起码可以简洁高效地求出数组前缀和。
最近常常碰到的是求一段区间内大于或小于某个数的个数。
如果数字范围较大,但不要求修改的话,可以用树状数组存离散化后的数的大小。
比如 1 2 4 4 6 7,离散化 1 2 3 3 4 5,统计个数 0 1 1 2 1 1
每添加一个新数,都看看有没有当前位置结束的区间,用树状数组求一下区间内小于某数的和。
当然,这样比较繁琐,还要考虑每个区间的起始位置,如果会持久化线段树就很方便。如果数字范围比较小,比如这道题,那么有修改也不怕,有几个数就开几个树状数组,存数的位置。
比如1 2 3 3 4 5
1号数组:1 0 0 0 0 0
2号数组:0 1 0 0 0 0
3号数组:0 0 1 1 0 0
4号数组:0 0 0 0 1 0
5号数组:0 0 0 0 0 1
需要修改的时候对对应数字的树状数组修改。
需要统计的时候统计每一个需要的树状数组。
题意:
链接:https://www.nowcoder.com/acm/contest/112/D
来源:牛客网
一个只含数字的字符串,q次操作,每次操作将第i位数字改为x,每次操作后,统计长度在[l, r]之间且首数字大于尾数字的子串的个数。
思路:
每一个字符对答案的贡献是它前 r 到前 l 内大于它的数字个数
和后 l 到后 r 内小于它的元素个数
插入的时候按值的大小插入到各自的树状数组中,注意只当作尾插入就好,不然会重复。
修改时先减去原字符对答案的贡献,再加上新的
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int maxn = 100010;
char c[maxn];
int a[maxn];
int n,q,l,r,p,x;
int t[10][maxn];
void upd(int x,int p,int d) {
while(p<maxn) {
t[x][p]+=d;
p+=p&-p;
}
}
LL que(int x,int p) {
LL res = 0;
while(p>0) {
res+=t[x][p];
p-=p&-p;
}
return res;
}
LL qdown(int p) {
LL res = 0;
for(int i=a[p]-1;i>=0;i--) {
res+=que(i,min(p+r-1,n))-que(i,min(p+l-2,n));
}
return res;
}
LL qup(int p) {
LL res = 0;
for(int i=a[p]+1;i<10;i++) {
res+=que(i,max(p-l+1,0))-que(i,max(p-r,0));
}
return res;
}
int main() {
scanf("%s%d%d%d",c+1,&q,&l,&r);
n = strlen(c+1);
for(int i=1;i<=n;i++) a[i] = c[i]-'0';
//printf("n = %d\n",n);
LL ans = 0;
for(int i=1;i<=n;i++) {
upd(a[i],i,1);
ans+=qup(i);
}
//printf("ans = %lld\n",ans);
while(q--) {
scanf("%d%d",&p,&x);
ans-=qup(p)+qdown(p);
upd(a[p],p,-1);
a[p] = x;
upd(a[p],p,1);
ans+=qup(p)+qdown(p);
printf("%lld\n",ans);
}
return 0;
}
很美,是吧