一个简单的整数问题2(树状数组&&线段树)

23 篇文章 2 订阅
16 篇文章 0 订阅
题目描述

给定一个长度为N的数列A,以及M条指令,每条指令可能是以下两种之一:
1、“C l r d”,表示把 A[l],A[l+1],…,A[r] 都加上 d。
2、“Q l r”,表示询问 数列中第 l~r 个数的和。
对于每个询问,输出一个整数表示答案。

输入格式

第一行两个整数N,M。
第二行N个整数A[i]。
接下来M行表示M条指令,每条指令的格式如题目描述所示。

输出格式

对于每个询问,输出一个整数表示答案。
每个答案占一行。

数据范围

1≤N,M≤105,
|d|≤10000,
|A[i]|≤1000000000

输入样例:
10 5
1 2 3 4 5 6 7 8 9 10
Q 4 4
Q 1 10
Q 2 4
C 3 6 3
Q 2 4
输出样例:
4
55
9
15

题目分析
树状数组解

这个题有两个操作:将[l,r]区间的数都加d 和 求出[l,r]区间的和。
我们先看第一个操作:将[l,r]区间的数都加d。要快速的完成这个操作,还是得用差分数组来完成。(即用线段树维护一个差分数组)
但对于第二个操作来说,一个差分数组并不能快速的求出a[l,r]的和。
设b[]为a[]的差分数组。求出1-x的a[i]的和:
a[1]:b[1]
a[2]:b[1] b[2]
a[3]:b[1] b[2] b[3]
……
a[x]:b[1] b[2] b[3] b[4]……b[x]
上面所有的b[]全部加起来才能得到a[1-x]的和。由上面的方阵我们可以得到:
  b[1] b[2] b[3] b[4]……b[x]
a[1]:b[1] b[2] b[3] b[4]……b[x]
a[2]:b[1] b[2] b[3] b[4]……b[x]
a[3]:b[1] b[2] b[3] b[4]……b[x]
……
a[x-1]:b[1] b[2] b[3]…b[x-1] b[x]
a[x]:b[1] b[2] b[3] b[4]……b[x]
这个方阵的和为(b[1]+b[2]+...+b[x])*(x+1),而补上的蓝色部分的和为(b[1]*1+b[2]*2+...+xb[x])。
因此a[1-x]的前缀和为:(b[1]+b[2]+...+b[x])*(x+1)-(b[1]*1+b[2]*2+...+xb[x])==sum(b[i])*(x+1)-sum(b[i]*i)[1<=i<=x]

所以,我们只需要维护b[i]和i*b[i]两个树状数组即可快速的完成这两个操作。

代码如下
#include <iostream>
#include <cstdio>
#include <cmath>
#include <string>
#include <cstring>
#include <map>
#include <queue>
#include <vector>
#include <set>
#include <algorithm>
#define LL long long
#define PII pair<int,int>
#define x first
#define y second
using namespace std;
const int N=1e5+5,M=350,INF=0x3f3f3f3f;
int n,m,a[N];
LL tr1[N],tr2[N];
int lowbit(int x)
{
    return x&-x;
}
void add(int x,int c)
{
    for(int i=x;i<=n;i+=lowbit(i))
        tr1[i]+=c,tr2[i]+=(LL)c*x;
}
LL sum(int x)
{
    LL res=0;
    for(int i=x;i;i-=lowbit(i))
        res+=tr1[i]*(x+1)-tr2[i];
    return res;
}
int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&a[i]);
        add(i,a[i]-a[i-1]);
    }
    while(m--)
    {
        char op[2];
        int l,r,d;
        scanf("%s",op);
        if(op[0]=='C')
        {
            scanf("%d%d%d",&l,&r,&d);
            add(l,d),add(r+1,-d);
        }
        else 
        {
            scanf("%d%d",&l,&r);
            printf("%lld\n",sum(r)-sum(l-1));
        }
    }
    return 0;
}
线段树解

除了树状数组的解法,我们也可以用线段树来解这道题。
因为我们要求的是区间和,因此辅助信息即为区间和sum。 这个辅助信息比较好求:u段的sum即为左子段的sum+右子段的sum。
但是我们还需要进行区间修改,这时加一个懒标记add来记录某一段区间要加多少数就行了。
一道线段树+懒标记的裸题。

代码如下
#include <iostream>
#include <cstring>
#define LL long long
using namespace std;
const int N=1e5+5;
struct Node{
    int l,r;
    LL sum,add;		//sum记录区间,add(懒标记)记录子段的sum要加的值
}tr[N*4];
int n,m;
int w[N];			//记录原序列
void pushup(int u)	//u段的sum即为左子段的sum+右子段的sum
{
    tr[u].sum=tr[u<<1].sum+tr[u<<1|1].sum;
}
void pushdown(int u)		//将懒标记向下传递
{
    auto &root=tr[u],&left=tr[u<<1],&right=tr[u<<1|1];
    if(root.add)
    {
        left.add+=root.add;			//更新子段的懒标记
        left.sum+=(LL)(left.r-left.l+1)*root.add;	//更新子段的sum
        right.add+=root.add;
        right.sum+=(LL)(right.r-right.l+1)*root.add;
        root.add=0;					//将u段的懒标记清0
    }
}
void build(int u,int l,int r)		//建树
{
    if(l==r) tr[u]={l,r,w[l],0};
    else
    {
        tr[u]={l,r};
        int mid=l+r>>1;
        build(u<<1,l,mid),build(u<<1|1,mid+1,r);
        pushup(u);
    }
}
void modify(int u,int l,int r,int v)	//区间修改
{
    if(tr[u].l>=l&&tr[u].r<=r)
    {
        tr[u].sum+=(LL)(tr[u].r-tr[u].l+1)*v;	//u段的sum加的值即为:u段的长度*v
        tr[u].add+=v;					//更新懒标记
    }
    else
    {
        pushdown(u);
        int mid=tr[u].l+tr[u].r>>1;
        if(l<=mid) modify(u<<1,l,r,v);	//如果l<=mid,说明[l,r]涉及左半段,因此要修改左半边
        if(r>mid) modify(u<<1|1,l,r,v);	//如果r>mid,说明[l,r]涉及右半段,因此要修改右半边
        pushup(u);
    }
}
LL query(int u,int l,int r)
{
    if(tr[u].l>=l&&tr[u].r<=r) return tr[u].sum;	//如果u段在[l,r]内,那么和即为tr[u].sum
    
    pushdown(u);
    int mid=tr[u].l+tr[u].r>>1;
    LL sum=0;
    if(l<=mid) sum+=query(u<<1,l,r);	//如果l<=mid,说明[l,r]涉及左半段,因此要求左半段的和
    if(r>mid) sum+=query(u<<1|1,l,r);	//如果r>mid,说明[l,r]涉及右半段,因此要求右半段的和
    return sum;
}
int main()
{
    scanf("%d %d",&n,&m);
    for(int i=1;i<=n;i++)
        scanf("%d",&w[i]);
    
    build(1,1,n);			//建树
    while(m--)
    {
        int l,r;
        char op[2];
        scanf("%s %d %d",op,&l,&r);
        if(op[0]=='C')
        {
            int d;
            scanf("%d",&d);
            modify(1,l,r,d);
        }
        else printf("%lld\n",query(1,l,r));
    }
    return 0;
}
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

lwz_159

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值