【树状数组】牛客挑战赛15D 数字串

最近做了不少树状数组的题目,不得不感叹,这真是一个优美的数据结构。起码可以简洁高效地求出数组前缀和。
最近常常碰到的是求一段区间内大于或小于某个数的个数。

  1. 如果数字范围较大,但不要求修改的话,可以用树状数组存离散化后的数的大小。
    比如 1 2 4 4 6 7,离散化 1 2 3 3 4 5,统计个数 0 1 1 2 1 1
    每添加一个新数,都看看有没有当前位置结束的区间,用树状数组求一下区间内小于某数的和。
    当然,这样比较繁琐,还要考虑每个区间的起始位置,如果会持久化线段树就很方便。

  2. 如果数字范围比较小,比如这道题,那么有修改也不怕,有几个数就开几个树状数组,存数的位置。
    比如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;
}

很美,是吧

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值