樹狀數組

洛谷P3374 【模板】樹狀數組 1

 

 

已知一個數列,你須要進行下面兩種操做:ios

1.將某一個數加上x數組

2.求出某區間每個數的和數據結構

 

先來定義一個東西:lowbit字體

記lowbit(x)爲x的二進制最低位包含後面的0構成的數.spa

舉幾個栗子:code

8的二進制表示是1000,lowbit(8) =1000(2)= 8blog

6的二進制是110,lowbit(6) =10(2)= 2ip

求lowbitget

記f[i]是i的最低位.string

若i是奇數,f[i] = 1,不然f[i] = f[i/2] * 2.

是否是有點麻煩QwQ

其實能夠這樣:

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

 因爲電腦一種叫作補碼的操做(因爲電腦是二進制,它們存的相反數是它的取反+1),一個數與它的相反數作與操做時會返回二進制下最右邊的1的位置。舉個例子: 6&-6=2 將6變成二進制:110。其中最右側的1加粗字體:110則返回的是二進制下10的值:

 

再來介紹一下樹狀數組

樹狀數組是一種用來求前綴和的數據結構.

對於原始數組A,咱們設一個數組C

C[i]=A[i-lowbit(i)+1]+...+A[i]

i>0的時候C[i]纔有用,C就是樹狀數組

大概就是這樣:

  • 第一位(1在二進制下=1 二進制下的1=1)的值爲輸入的數組的第一位往前的一位的和,也就是第一位。
  • 第二位(2在二進制下=10 二進制下的10=2)的值爲輸入的數組的第二位往前兩位的和,第一位和第二位。
  • 第三位(3在二進制下=11 二進制下的1=1)的值爲輸入的數組的第三位往前一位的和,也就是第三位。
  • 第四位的值(4在二進制下=100 二進制下的100=4)的值爲輸入的數組的第四位往前四位的和,也就是第一位,第二位,第三位以及第四位

 

 

 樹狀數組用於解決單個元素常常修改,並且還反覆求不一樣區間和的狀況

 

樹狀數組求和

樹狀數組只可以支持詢問前綴和,不支持區間和,可是能夠用前綴和求區間和.

咱們先找到C[n],而後咱們發現如今,下一個要找的點是n − lowbit(n),而後咱們不斷的減去lowbit(n)並累加C數組.

咱們能夠用前綴和相減的方式來求區間和.

詢問的次數和n的二進制裏1的個數相同,時間複雜度是O(log N).

代碼:

int ask(int x) //查詢[1,x]的值 
{
        int ans=0;
        for (;x;x-=lowbit(x)) ans+=s[x];
 //咱們詢問的]是[x-lowbit(x)+1,x,而後後面一個區間是以x-lowbit(x)爲右端點的,依次類推 
        return ans;
}

 

 

 

樹狀數組更新

如今咱們要修改Ax的權值,考慮全部包含x這個位置的區間個數.

從C[x]開始,下一個應該是C[y = x + lowbit(x)],再下一個是C[z = y + lowbit(y)]...直到達到上限

注意到每一次更新以後,位置的最低位1都會往前提1.總複雜度也爲O(log N).

大概就是這樣:

 

代碼:

void ins(int x,int y)//修改a[x]的值,a[x]+=y;
{
        for (;x<=n;x+=lowbit(x)) s[x]+=y;
 //首先須要修改的區間是以x爲右端點的,而後下一個區間是x+lowbit(x),以此類推 
}

 

因此這一個題的代碼就出來了:

 

#include<cstdio>
#include<iostream>
#include<cstdlib>
#include<iomanip>
#include<cmath>
#include<cstring>
#include<string>
#include<algorithm>
#include<time.h>
#include<queue>
using namespace std;
typedef long long ll;
typedef long double ld;
typedef pair<int,int> pr;
const double pi=acos(-1);
#define rep(i,a,n) for(int i=a;i<=n;i++)
#define per(i,n,a) for(int i=n;i>=a;i--)
#define Rep(i,u) for(int i=head[u];i;i=Next[i])
#define clr(a) memset(a,0,sizeof a)
#define pb push_back
#define mp make_pair
#define fi first
#define sc second
ld eps=1e-9;
ll pp=1000000007;
ll mo(ll a,ll pp){if(a>=0 && a<pp)return a;a%=pp;if(a<0)a+=pp;return a;}
ll powmod(ll a,ll b,ll pp){ll ans=1;for(;b;b>>=1,a=mo(a*a,pp))if(b&1)ans=mo(ans*a,pp);return ans;}
ll read(){
    ll ans=0;
    char last=' ',ch=getchar();
    while(ch<'0' || ch>'9')last=ch,ch=getchar();
    while(ch>='0' && ch<='9')ans=ans*10+ch-'0',ch=getchar();
    if(last=='-')ans=-ans;
    return ans;
}
//head

using namespace std;

int m,n,s[5000005];

long long ans;

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

void ins(int x,int y)//修改a[x]的值,a[x]+=y;
{
        for (;x<=n;x+=lowbit(x)) s[x]+=y; //首先須要修改的區間是以x爲右端點的,而後下一個區間是x+lowbit(x),以此類推 
}

int ask(int x) //查詢[1,x]的值 
{
        int ans=0;
        for (;x;x-=lowbit(x)) ans+=s[x]; //咱們詢問的]是[x-lowbit(x)+1,x,而後後面一個區間是以x-lowbit(x)爲右端點的,依次類推 
        return ans;
}

int main()
{
    n=read(),m=read();
    rep(i,1,n)
    {
        int t=read();
        ins(i,t);
    }
        
    rep(i,1,m)
    {
        int a,b,c;
        a=read(),b=read(),c=read();
        if(a==1)
        {
            ins(b,c);
        }
        if(a==2)
        {
            printf("%d\n",ask(c)-ask(b-1));//區間查詢則爲右邊界前綴和減去左邊界前綴和 
        }
    }
}

 

 

 

 

洛谷P3368 【模板】樹狀數組 2

 

已知一個數列,你須要進行下面兩種操做:

1.將某區間每個數加上x

2.求出某一個數的值

樹狀數組區間修改

若是將x到y區間加上一個k,那就是從x到n都加上一個k,再從y+1到n加上一個-k

加的移動仍是i+=lowbit(i);

或者說叫差分

差分:

 

設數組a[]={1,6,8,5,10},那麼差分數組b[]={1,5,2,-3,5}

 

也就是說b[i]=a[i]-a[i-1];(a[0]=0;),那麼a[i]=b[1]+....+b[i];(這個很好證的)。

 

假如區間[2,4]都加上2的話

 

a數組變爲a[]={1,8,10,7,10},b數組變爲b={1,7,2,-3,3};

 

發現了沒有,b數組只有b[2]和b[5]變了,由於區間[2,4]是同時加上2的,因此在區間內b[i]-b[i-1]是不變的.

 

因此對區間[x,y]進行修改,只用修改b[x]與b[y+1]:

 

b[x]=b[x]+k;b[y+1]=b[y+1]-k;

 

就像這樣

ins(b,d);
ins(c+1,-d);

 

樹狀數組單點查詢

 

查詢和求和是同樣的(代碼其實也同樣)。注意咱們的操做不是區間求和,而是求兩個前綴和的差!

int ask(int x) 
{
        int ans=0;
        for (;x;x-=lowbit(x)) ans+=s[x];
        return ans;
}

 

因此最後的代碼是這樣的:

#include<cstdio>
#include<iostream>
#include<cstdlib>
#include<iomanip>
#include<cmath>
#include<cstring>
#include<string>
#include<algorithm>
#include<time.h>
#include<queue>
using namespace std;
typedef long long ll;
typedef long double ld;
typedef pair<int,int> pr;
const double pi=acos(-1);
#define rep(i,a,n) for(int i=a;i<=n;i++)
#define per(i,n,a) for(int i=n;i>=a;i--)
#define Rep(i,u) for(int i=head[u];i;i=Next[i])
#define clr(a) memset(a,0,sizeof a)
#define pb push_back
#define mp make_pair
#define fi first
#define sc second
ld eps=1e-9;
ll pp=1000000007;
ll mo(ll a,ll pp){if(a>=0 && a<pp)return a;a%=pp;if(a<0)a+=pp;return a;}
ll powmod(ll a,ll b,ll pp){ll ans=1;for(;b;b>>=1,a=mo(a*a,pp))if(b&1)ans=mo(ans*a,pp);return ans;}
ll read(){
    ll ans=0;
    char last=' ',ch=getchar();
    while(ch<'0' || ch>'9')last=ch,ch=getchar();
    while(ch>='0' && ch<='9')ans=ans*10+ch-'0',ch=getchar();
    if(last=='-')ans=-ans;
    return ans;
}
//head

using namespace std;

int m,n,t[5000005],s[5000005];

long long ans;

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

void ins(int x,int y)
{
        for (;x<=n;x+=lowbit(x)) s[x]+=y; 
}

int ask(int x) 
{
        int ans=0;
        for (;x;x-=lowbit(x)) ans+=s[x];
        return ans;
}

int main()
{
    n=read(),m=read();
    rep(i,1,n)
    {
        t[i]=read();
    }
        
    rep(i,1,m)
    {
        int a;
        a=read();
        if(a==1)
        {
            int b=read(),c=read(),d=read();
            ins(b,d);
            ins(c+1,-d);
        }
        if(a==2)
        {
            int b=read();
            printf("%d\n",t[b]+ask(b));
        }
    }
}

 

PS.這裏s數組存的是原數組t每個元素增長或減小的多少

相關文章
相關標籤/搜索
本站公眾號
   歡迎關注本站公眾號,獲取更多信息