树状数组区间更新区间查询以及gcd的logn性质

Posted Billyshuai

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了树状数组区间更新区间查询以及gcd的logn性质相关的知识,希望对你有一定的参考价值。

题目描述

给你一个长为n的序列a

m次查询

每次查询一个区间的所有子区间的gcd的和mod1e9+7的结果

输入描述:

第一行两个数n,m
之后一行n个数表示a
之后m行每行两个数l,r表示查询的区间

输出描述:

对于每个询问,输出一行一个数表示答案
示例1

输入

5 7
30 60 20 20 20
1 1
1 5
2 4
3 4
3 5
2 5
2 3

输出

30
330
160
60
120
240
100

说明

[1,1]的子区间只有[1,1],其gcd为30
[1,5]的子区间有:
[1,1]=30,[1,2]=30,[1,3]=10,[1,4]=10,[1,5]=10
[2,2]=60,[2,3]=20,[2,4]=20,[2,5]=20
[3,3]=20,[3,4]=20,[3,5]=20
[4,4]=20,[4,5]=20
[5,5]=20
总共330
[2,4]的子区间有:
[2,2]=60,[2,3]=20,[2,4]=20
[3,3]=20,[3,4]=20
[4,4]=20
总共160
[3,4]的子区间有:
[3,3]=20,[3,4]=20
[4,4]=20
总共60
[3,5]的子区间有:
[3,3]=20,[3,4]=20,[3,5]=20
[4,4]=20,[4,5]=20
[5,5]=20
总共120
[2,5]的子区间有:
[2,2]=60,[2,3]=20,[2,4]=20,[2,5]=20
[3,3]=20,[3,4]=20,[3,5]=20
[4,4]=20,[4,5]=20
[5,5]=20
总共240
[2,3]的子区间有:
[2,2]=60,[2,3]=20
[3,3]=20
总共100

http://blog.csdn.net/fsahfgsadhsakndas/article/details/52650026 //详解

我们假设sigma(r,i)表示r数组的前i项和,调用一次的复杂度是log2(i)

设原数组是a[n],差分数组c[n],c[i]=a[i]-a[i-1],那么明显地a[i]=sigma(c,i),如果想要修改a[i]到a[j](比如+v),只需令c[i]+=v,c[j+1]-=v

【今天的主要内容】

我们可以实现NlogN时间的“单点修改,区间查询”,“区间修改,单点查询”,其实后者就是前者的一个变形,要明白树状数组的本质就是“单点修改,区间查询”

怎么实现“区间修改,区间查询”呢?

观察式子:
a[1]+a[2]+...+a[n]

= (c[1]) + (c[1]+c[2]) + ... + (c[1]+c[2]+...+c[n]) 

= n*c[1] + (n-1)*c[2] +... +c[n]

= n * (c[1]+c[2]+...+c[n]) - (0*c[1]+1*c[2]+...+(n-1)*c[n])    (式子①)

那么我们就维护一个数组c2[n],其中c2[i] = (i-1)*c[i]

每当修改c的时候,就同步修改一下c2,这样复杂度就不会改变

那么

式子①

=n*sigma(c,n) - sigma(c2,n)



#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=1e5+88;
const int mod=1e9+7;
int n,m,a[N],v[250],pos[250];
long long f[N],g[N],ans[N];
struct node{
    int l,r,id;
    bool operator < (const node &A)const{
    return r<A.r;
    }
}q[N];
void add(int x,int y){
    for(int i=x;i<=n;i+=i&(-i)) f[i]+=y,g[i]+=1LL*(x-1)*y;
}
long long query(int u){
    long long ans=0;
    for(int i=u;i;i-=i&(-i)) ans=(ans+1LL*u*f[i]-g[i])%mod;
    return ans;
}
int main(){
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;++i) scanf("%d",a+i);
    for(int i=1;i<=m;++i) scanf("%d%d",&q[i].l,&q[i].r),q[i].id=i;
    sort(q+1,q+m+1);
    int tot=0,now;
    for(int i=1,p=1;i<=m;++i) {
        while(p<=q[i].r) {
            for(int j=1;j<=tot;++j) v[j]=__gcd(v[j],a[p]);
            v[++tot]=a[p],pos[tot]=p;
            now=tot,tot=0;
            for(int j=1;j<=now;++j) if(v[j]!=v[j-1]) v[++tot]=v[j],pos[tot]=pos[j];
            for(int j=1;j<tot;++j) add(pos[j],v[j]),add(pos[j+1],-v[j]);
            add(pos[tot],v[tot]),add(p+1,-v[tot]);
            ++p;
        }
        ans[q[i].id]=query(q[i].r)-query(q[i].l-1);
    }
    for(int i=1;i<=m;++i) printf("%lld\n",(ans[i]+mod)%mod);
} 

 

以上是关于树状数组区间更新区间查询以及gcd的logn性质的主要内容,如果未能解决你的问题,请参考以下文章

树状数组

树状数组与线段树

树状数组求区间最大值(树状数组)(复习)

树状数组

树状数组2 - 区间加 单点求和

数据结构之树状数组从零认识树状数组