1082 线段树练习 3 (区间修改&&区间查询)

 题目链接

懒得打思路,在代码后面有 EnjoyingAC 写的,大致意思都是一样的,就找过来附在下面了,看不懂代码的可以看看下面的解释


//区间修改,区间查询

#include<iostream>
#include<cstring>
#include<cstdio>

using namespace std;

const int N = 200000+5;
typedef long long LL;

LL c[N][2];

int n, m;

int lowbit(int x)
{
    return x&(-x);
}

void Update(int x, int k, int pos)
{
    while(x <= n)
    {
        c[x][pos] += k;
        x += lowbit(x);
    }
}

LL query(int x, int pos)
{
    LL sum=0;

	while(x > 0)
    {
        sum += c[x][pos];
        x -= lowbit(x);
    }
    return sum;
 }

LL query(int x)
{
    LL sum=0;
	int tmp = x+1;
	while(x > 0) {
        sum += c[x][0];
        x -= lowbit(x);
    }
    
    sum *= tmp;
    
    tmp--;
    while(tmp > 0){
    	sum -= c[tmp][1];
        tmp -= lowbit(tmp);
	}
    return sum;
 }
 
LL querySQ(int l, int r){
//	return query(r)-query(l-1);
	return ((r+1)*query(r, 0)-query(r,1) - ((l)*query(l-1, 0)-query(l-1,1)));
}

int main()
{
    scanf("%d", &n);
    int a=0, b, op, x;
    for(int i=1; i<=n; i++)
    {
        scanf("%d", &b);
  //差分用于区间修改      
        Update(i, b-a, 0);
        Update(i, (b-a)*i, 1);
        a=b;
    }
    
    scanf("%d", &m);
    for(int i=1; i<=m; i++)
    {
        scanf("%d", &op);
        
		int l, r;
		 
		if(op == 1)
        {        	
            scanf("%d%d%d", &l, &r, &x);
            Update(l, x, 0);
            Update(r+1, -x, 0);
            Update(l, x*l, 1);
            Update(r+1, -x*(r+1), 1);
        }
        
        else if(op == 2)
        {
            scanf("%d%d", &l, &r);
            
            printf("%lld\n", querySQ(l, r));
        }
    }
    return 0;
} 

 //思路如下:

转自https://blog.csdn.net/qq_37685156/article/details/80397765

区间修改

任务: 
对于数组a[1],a[2],a[3],….a[n],给定区间[l,r]。 
要求将a[l]~a[r]的值都加v。 
解决: 
引入一个差分数组b,b[i]=a[i]-a[i-1](默认a[0]=0)。 
比如: 
a:1,2,3,4,5 
b:  1,1,1,1,1 
将区间[2,3]的值都加2 
a: 1,4,5,4,5 
b: 1,3,1,-1,1 
对于a中区间[l,r]都加v,b数组中b[l]+=v,b[r+1]-=v. 
故只需对原数组a的差分数组b用树状数组去进行单点修改,就可以快速地对数组a进行区间修改。

 

单点查询

任务: 
对进行了区间修改的数组进行单点查询 
解决: 
观察差分数组,可以推出:a[i]=b[1]+b[2]+…+b[i]。 
故只需求出原数组a的差分数组b的前缀i即可查询a[i]。

 

区间查询

任务: 
对进行了区间修改的数组进行区间查询 
解决: 
引入第二个数组c,c[i]=i*b[i].称为数组b的i倍数组。 
比如: 
a:1,2,3,4,5 
b:1,1,1,1,1 
c:1,2,3,4,5 
将区间[2,3]的值都加2后, 
a:1,4,5,4,5 
b:1,3,1,-1,1 
c:1,6,3,-4,5 
查询数组a的前3位数的和,即a[1]+a[2]+a[3]=1+4+5=10。 
a[1]+a[2]+a[3]=4*(b[1]+b[2]+b[3]) - (c[1]+c[2]+c[3])=4*5 - 10=10. 
设sumA表示数组a的前缀和,sumB表示数组b的前缀和,sumC表示数组c的前缀和。 
sumA[i]=(i+1)*sumB[i] - sumC[i]。 
上述公式其实是可以严格证明的。 
证明: 
假设要求数组a的前x项和。 
因为a[i]=b[1]+b[2]+…+b[i], 
a[1]+a[2]+a[3]+…+a[x]=b[1]+(b[1]+b[2])+(b[1]+b[2]+b[3])+…+(b[1]+b[2]+b[3]+…+b[x]). 
                                   =b[1]x + b[2](x-1) + b[3](x-2) + b[i](x-i+1) +…+b[1]*x. 
所以需要b[x]一次,b[x-1]两次,b[x-2]三次,…,b[i] (x-i+1)次,b[1] x次。

故只需用两个树状数组去维护原数组a的差分数组b的前缀和以及b的i倍数组c的前缀和即可得到原数组a的前缀和。
 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值