樹狀數組小結

樹狀數組功能:(適用於:要求不斷求區間和 || 不斷更新區間

1.快速 求區間和

2.快速 更新區間

具體詳細資料 見大白書 和 輔導資料.

樹狀數組 ---運用了分塊的思想。

用lowbit函數來得到 地址下標.

基本操作:

1.lowbit()函數

原理:利用了負數在計算機中的存儲形式(按位取反+1),你會發現負數與其絕對值在存儲上 

最後一個1的位置是相同的 那麼用& 就可以 把一個數字 分爲幾塊。樹狀數組的操作就全基於此!

2.sum()求區間和函數

3.add()更新區間函數

例題:

poj2352

思路 ,因爲星星事先已經按y從小到大排序了,那麼只需要從第一個星星開始 得到它的級數;求級數就要用到區間求和,得到級數後,又要把

該星星加入樹狀數組裏,即更新(是爲了後面星星算級數服務)。

//Accepted	384K	141MS
#include <iostream>
#include<cstdio>
#include<cstring>
using namespace std;
#define MAX 32010
int n;
int tree[MAX];
int level[MAX];
int lowbit(int x)
{
    return x&(-x);
}
void Add(int x)
{
    while(x<MAX)
    {
        tree[x]+=1;
        x+=lowbit(x);
    }
}
int Sum(int x)
{
    int s=0;
    while(x>0)
    {
        s+=tree[x];
        x-=lowbit(x);
    }
    return s;
}
int main()
{
    scanf("%d",&n);
    memset(tree,0,sizeof(tree));
    memset(level,0,sizeof(level));
    for(int i=1;i<=n;i++)
    {
        int x,y;
        scanf("%d%d",&x,&y);
        x++;
        level[Sum(x)]++;
        Add(x);
    }
    for(int i=0;i<n;i++)
        printf("%d\n",level[i]);
    return 0;
}

hrbust 1400

和上題大同小異,只是需要注意這裏沒事先排好序,且排序是要把 爲同一起點的車 要按速度從大到小排序!

//Accepted		262ms	
#include <iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
#define MAX 100002
#define N 1000002
int n;
int c[N];
struct Node
{
    int x,v;
    bool operator<(const Node& a) const
    {
        if(a.x==x) return v>a.v;
        else
        return x<a.x;
    }
}car[MAX];
int lowbit(int x)
{
    return x&(-x);
}
void Add(int x)
{
    while(x<N)
    {
        c[x]+=1;
        x+=lowbit(x);
    }
}
int sum(int x)
{
    long long s=0;
    while(x>0)
    {
        s+=c[x];
        x-=lowbit(x);
    }
    return s;
}
int solve()
{
    memset(c,0,sizeof(c));
    int ans=0;
    for(int i=1;i<=n;i++)
    {
        ans+=i-sum(car[i].v)-1;
        Add(car[i].v);
    }
    return ans;
}
int main()
{
    while(~scanf("%d",&n))
    {
        for(int i=1;i<=n;i++)
        {
            scanf("%d%d",&car[i].x,&car[i].v);
        }
            sort(car+1,car+1+n);
           // cout<<"\n*****************************\n";
           // for(int i=1;i<=n;i++)
              //  printf("%d ",car[i].x);
            //cout<<endl;
            //for(int i=1;i<=n;i++)
                //printf("%d ",car[i].v);
             //   cout<<"\n*****************************\n";
            printf("%lld\n",solve());
    }
    return 0;
}

hrbust 1161

就需要注意的是:樹狀數組的更新操作  有一個所謂的時間限制!

加一個記錄時間的 數組就OK!

//	560ms	
#include <iostream>
#include<cstdio>
#include<cstring>
using namespace std;
#define MAX 100002
int n,q,t;
int tim[MAX];
int c[MAX];
int lowbit(int x)
{
    return x&(-x);
}
int sum(int x)
{
    int s=0;
    while(x>0)
    {
        s+=c[x];
        x-=lowbit(x);
    }
    return s;
}
void add(int x)
{
    while(x<MAX)
    {
        c[x]+=1;
        x+=lowbit(x);
    }
}
int main()
{
    int T;
    scanf("%d",&T);
    int cas=1;
    while(T--)
    {
        printf("Case %d:\n",cas++);/**注意這個輸出位置*/
        memset(c,0,sizeof(c));
        int Attack=0;
        scanf("%d%d%d",&n,&q,&t);
        for(int i=1;i<=n;i++)
            tim[i]=-t;/**爲後面鋪墊**/

        for(int i=1;i<=q;i++)
        {
            char s[6];
            scanf("%s",s);
            if(s[0]=='A')
            {
                Attack++;
                int a;
                scanf("%d",&a);
                if(tim[a]+t<=Attack)/**判斷能不能抵消攻擊**/
                {
                    tim[a]=Attack;/**能抵消則從這時刻重新計算冷卻時間**/
                }
                else
                    add(a);
            }
            else
            {
                int fir,last;
                scanf("%d%d",&fir,&last);
                if(fir>last) swap(fir,last);
                printf("%d\n",sum(last)-sum(fir-1));
            }
        }
    }
    return 0;
}


poj2085

這一題 其實主要是構造題!不過其中要用到 樹狀數組!


大意:給一個n,和一個m。n爲1~n的序列.m爲多少個 逆序對.

要你求出 由1~n數字組成的 擁有m個逆序對的 字典序最小的排列!

第一步:要求出m<=1+2+3+......+k; 中的k(表示這個逆序對最多由幾個數字組成)--明顯用樹狀數組求 區間和!+二分查找k

第二步:應該按升序輸出1~n-k個數放答案序列的最前面!因爲只需要後面的k序列就可以滿足m個逆序對的條件!

第三步:要求出m=1+2+3+.....+x中的x  這個x一定是介於 k和k-1之間的。不然上一步求出的k就不是最大的。

那麼 計算 k-x;就可以知道實際這個要得到的序列  與  k個數完全降序排列 構成的逆序  少幾個逆序對!

那麼把 n-(k-x) 這個數放到 這個完全降序的逆序列的最前面 就可以 減少k-x個逆序對。就得到了答案。

第四步: 注意輸出和格式就行了!


//xdq	2085	Accepted	524K	110MS	C++	1438B
#include <iostream>
#include<cstdio>
#include<cstring>
using namespace std;
#define MAX 50002
#define ll __int64
ll c[MAX];
ll lowbit(ll x)
{
    return x&(-x);
}
ll sum(ll x)
{
    ll s=0;
    while(x>0)
    {
        s+=c[x];
        x-=lowbit(x);
    }
    return s;
}
void add(ll x)
{
    ll val=x;
    while(x<MAX)
    {
        c[x]+=val;
        x+=lowbit(x);
    }
}
void Init()
{
    for(ll i=1;i<=MAX;i++)
        add(i);
}
ll find_k(ll key,ll l)
{
    ll k,mid;
    ll r=1;
    while(l>=r)
    {
        mid=(l-r)/2+r;
        if(sum(mid)>=key)
        {
            k=mid;
            l=mid-1;
        }
        else {r=mid+1;}
    }
    return k;
}
int main()
{
    //freopen("ss.txt","r",stdin);
    //freopen("tt.txt","w",stdout);
    Init();
    ll n;
    ll m;
    while(~scanf("%I64d%I64d",&n,&m))
    {
        if(n==-1&&m==-1) break;
        ll k,i;
        if(m==0)
        {
            for(i=1;i<n;i++)
                printf("%I64d ",i);
            printf("%I64d\n",i);
            continue;
        }
        k=find_k(m,n);
        for( i=1;i<n-k;i++) /**前n-k個按升序排*/
            printf("%I64d ",i);

        int x=k-(sum(k)-m);
            printf("%I64d ",n-k+x);/**把這個數放在n前面就可以消除多出的 k-x個逆序對**/

        for( i=n;i>n-k+x;i--) /**然後就把剩餘的降序排列就ok 了!**/
            printf("%I64d ",i);
        for( i=n-k+x-1;i>n-k;i--)
            printf("%I64d ",i);
         printf("%I64d\n",n-k);
    }
    return 0;
}

如何用樹狀數組求逆序數呢?(當然可以用歸併排序求!)

第一種方法:只要從輸入序列的尾 開始讀數字,讀一個就求它(不包含它)之前的 區間和!

那麼這就是它後面有幾個小於它的數!即逆序對!


第二種方法:當然你從輸入序列頭往後讀 也可以,不過就是反向思維,就是求它之前有幾個比它小的,然後拿當前總數減去 就得到

前面有幾個比它大的,即逆序對!


這個題目因爲 每個數字可能 大到10^9 那麼樹狀數組 下標是開不下的!

所以這裏用到了  離散化  ,把輸入序列的 每個數組之間的 相對大小求出來了!

所有這下 樹狀數組下標就只跟 數據規模 n 有關了!


SGU180

#include <iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
#define N 70000
struct Node
{
    int data;
    int id;
    bool operator<(const Node t) const
    {
        return data<t.data;
    }
}a[N];
int b[N];
int n;
long long c[N];
int lowbit(int x)
{
    return x&(-x);
}
long long sum(int x)
{
    long long s=0;
    for(int i=x;i>0;i-=lowbit(i))
        s+=c[i];
    return s;
}
void add(int x)
{
    for(int i=x;i<=n;i+=lowbit(i))
        c[i]+=1;
}
int main()
{


    while(~scanf("%d",&n))
    {
        int tot=0,p=-1;
        memset(c,0,sizeof(c));
        long long ans=0;

        for(int i=1;i<=n;i++)
        {
            scanf("%d",&a[i].data);
            a[i].id=i;
        }
        sort(a+1,a+1+n);
        for(int i=1;i<=n;i++)
        {
            if(p!=a[i].data)/**相同的一定要相對大小相同
                               不然過不了第二組數據*/
            {
                tot++;
                p=a[i].data;
            }
            b[a[i].id]=tot;
        }

        for(int i=n;i>=1;i--)
        {
            ans+=sum(b[i]-1);/**注意這裏是要求不包含本身的和!**/
            add(b[i]);

        }
        cout<<ans<<endl;
    }


    return 0;
}



poj1195

二維的樹狀數組。即把分塊的思想 擴展運用!

//xdq	1195	Accepted	4880K	532MS	C++	1036B
/**純裸題。理解二維的lowbit分塊!**/
#include<stdio.h>
#include<string.h>
#define N 1100
int c[N][N],n,arr[N][N];
int lowbit(int x)
{
    return x&(-x);
}
void update(int x,int y,int num)
{
    int i,j;
    for(i=x;i<=n;i+=lowbit(i))
        for(j=y;j<=n;j+=lowbit(j))
            c[i][j]+=num;
}
int sum(int x,int y)
{
    int i,j,s=0;
    for(i=x;i>0;i-=lowbit(i))
        for(j=y;j>0;j-=lowbit(j))
            s+=c[i][j];
    return s;
}
int getsum(int x1,int y1,int x2,int y2)
{
    return sum(x2,y2)-sum(x1-1,y2)-sum(x2,y1-1)+sum(x1-1,y1-1);
}
int main()
{
    int op,x,y,l,b,r,t,a;
    while(scanf("%d",&op)!=EOF)
    {
        if(op==0)
        {
            scanf("%d",&n);
            memset(c,0,sizeof(c));
        }
        else if(op==1)
        {
            scanf("%d%d%d",&x,&y,&a);
            update(x+1,y+1,a);
        }
        else if(op==2)
        {
            scanf("%d%d%d%d",&l,&b,&r,&t);
            int ans=getsum(l+1,b+1,r+1,t+1);
            printf("%d\n",ans);
        }
    }
    return 0;
}




發佈了84 篇原創文章 · 獲贊 7 · 訪問量 7萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章