ICPC网络赛 Ryuji doesn't want to study (线段树/树状数组)

Ryuji doesn’t want to study

题目链接: 计蒜客 - 题库链接

题意

给你N个数,M个询问,有两种操作

  1. 将一个数修改为另一个值
  2. 求区间和 a[l]L+a[l+1](L1)+...+a[r1]2+a[r] a [ l ] ∗ L + a [ l + 1 ] ∗ ( L − 1 ) + . . . + a [ r − 1 ] ∗ 2 + a [ r ]

数据范围: N,M<105 N , M < 10 5


思路

前缀和思想

要求的和很奇怪,第一个数要出现N次,第二个数要出现N-1次,这种单调性质,我就想到了前缀和。那么现在我的目标就是如何快速求前缀和的区间和,我本来想在开一个树状数组快速的查询前缀和的前缀和,但是如果要修改一个点x的值,就需要修改 [x,N] [ x , N ] 的值仅依靠树状数组显然不行,我就用了线段树区间修改。所以我这是单点查询区间修改。

从图像找规律

20180909220719887

看图,用两个树状数组实现。


代码

线段树
#include <bits/stdc++.h>
using namespace std;
#define rep(i,j,k) for(ll i = (ll)j;i <= (ll)k;i ++)
#define per(i,j,k) for(ll i = (ll)j;i >= (ll)k;i --)
#define debug(x) cerr<<#x<<" = "<<(x)<<endl
#define mmm(a,b) memset(a,b,sizeof(a))
#define pb push_back

typedef double db;
typedef long long ll;
const ll MAXN = (ll)1e5+7;
const ll INF = (ll)0x3f3f3f3f;

#define lson rt<<1
#define rson rt<<1|1

ll N,M;
ll A[MAXN];

ll sum[MAXN<<2];
ll add[MAXN<<2];

void PushUp(ll rt){sum[rt] = sum[lson] + sum[rson]; }

void Build(ll l,ll r,ll rt){
    if (l == r){
        sum[rt] = A[l];
        return ;
    }
    ll m = (l+r)>>1;
    Build(l,m,lson);
    Build(m+1,r,rson);
    PushUp(rt);
}

void PushDown(ll rt,ll ln,ll rn){
    if (add[rt]){
        sum[lson] += add[rt]*ln;
        sum[rson] += add[rt]*rn;

        add[lson] += add[rt];
        add[rson] += add[rt];
        add[rt] = 0;
    }
}

void Update(ll L,ll R,ll C,ll l,ll r,ll rt){
    if (L <= l && r <= R){
        sum[rt] += C*(r+1-l);
        add[rt] += C;
        return ;
    }
    ll m = (l+r)>>1;
    PushDown(rt,m+1-l,r-m);
    if (L <= m) Update(L,R,C,l,m,lson);
    if (R >  m) Update(L,R,C,m+1,r,rson);
    PushUp(rt);
}

ll Query(ll L,ll R,ll l,ll r,ll rt){
    if (L == 0) return 0;
    if (L <= l && r <= R){
        return sum[rt];
    }

    ll m = (l+r)>>1;
    PushDown(rt,m+1-l,r-m);
    ll ans = 0;
    if (L <= m) ans += Query(L,R,l,m,lson);
    if (R >  m) ans += Query(L,R,m+1,r,rson);
    return ans;
}

int main()
{
    scanf("%lld %lld",&N,&M);
    ll sum = 0;
    rep(i,1,N) {
        ll tmp;
        scanf("%lld",&tmp);
        A[i] = A[i-1]+tmp;
    }
    Build(1,N,1);
    while (M --){
        ll a,b,c;
        scanf("%lld %lld %lld",&a,&b,&c);
        if (a == 1) {
            ll sum = 0;
            sum = Query(b,c,1,N,1);
            sum -= Query(b-1,b-1,1,N,1)*(c-b+1);
            printf("%lld\n",sum);
        }else {
            Update(b,N,c-Query(b,b,1,N,1)+Query(b-1,b-1,1,N,1),1,N,1);
        }
    }
}
树状数组
#include <bits/stdc++.h>
using namespace std;
#define rep(i,j,k) for(ll i = (ll)j;i <= (ll)k;i ++)
#define per(i,j,k) for(ll i = (ll)j;i >= (ll)k;i --)
#define debug(x) cerr<<#x<<" = "<<(x)<<endl
#define mmm(a,b) memset(a,b,sizeof(a))
#define pb push_back

typedef double db;
typedef long long ll;
const ll MAXN = (ll)1e6+7;
const ll INF = (ll)0x3f3f3f3f;

ll A[MAXN],B[MAXN],N,M;
ll lowbit(ll x) {return x&(-x); }

void update(ll L,ll val) {
    while (L <= N) {
        A[L] += val;
        L += lowbit(L);
    }
}

ll getsum(ll L) {
    ll sum = 0;
    while (L > 0) {
        sum += A[L];
        L -= lowbit(L);
    }
    return sum;
}

void update2(ll L,ll val) {
    while (L <= N) {
        B[L] += val;
        L += lowbit(L);
    }
}

ll getsum2(ll L) {
    ll sum = 0;
    while (L > 0) {
        sum += B[L];
        L -= lowbit(L);
    }
    return sum;
}

int main()
{
    scanf("%lld %lld",&N,&M);
    rep(i,1,N) {
        ll tmp;
        scanf("%lld",&tmp);
        update(i,tmp);
        update2(i,tmp*(N-i+1));
    }
    rep(i,1,M) {
        ll op,x,y;
        scanf("%lld %lld %lld",&op,&x,&y);
        if (op == 1) {
            ll ans1 = getsum2(y) - getsum2(x-1);
            ll ans2 = getsum(y) - getsum(x-1);
            printf("%lld\n",ans1-ans2*(N-y));
        }else {
            ll dif = getsum(x) - getsum(x-1);
            update(x,y-dif);
            update2(x,(y-dif)*(N-x+1));
        }
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值