树状数组萌新讲解+基础习题【一点一滴】

树状数组基础篇

树状数组讲点

中文名:树状数组
英文名:Binary Indexeds Tree
英译中:二进制索引树
这特么多清楚

引入:
给你n个数
1. 求区间的的和
2. 改变某个值

然后朴素做法肯定GG,这里就有了树状数组的神奇功效。

如果在时间空间允许的朴素做法也行啊,树状数组就是这么牛逼,他就是可以用它独有的特性而做好这件事情。直接开门见山吧。

首先得搞清楚树状数组的x(下标)运算:x&(-x);
当x是奇数的时候,最后一个比特是1,然后负数是取反+1,所以x&(-x),前面全部是0,最后是一,得出奇数的答案都是1.
当x为偶数的时候,①如果他的形式就是2^m,那么他的结果就是x;②如果不是,都是自己模拟的,难说,自己搞吧。答案就是:如果x二进制最右边的1,右边有k个0,答案就是2^k.
我们把A数组作为本来存储每个元素的数组;
从A数组转变成C数组(树状数组);联系那个下标运算
c1=a1,c2=a1+a2,c3=a3,c4=a1+a2+a3+a4,c5=a5,c6=a5+a6,c7=a7,c8=a1+a2+a3+a4+a5+a6+a7+a8,c9=a9,c10=a9+a10,c11=a11……..c16=a1+a2+a3+a4+a5+…….+a16。 当 i 为奇数时,ci=ai ;当 i 为偶数时,就要看 i 的因子中最多有二的多少次幂,例如,6 的因子中有 2 的一次幂,等于 2 ,所以 c6=a5+a6(由六向前数两个数的和),4 的因子中有 2 的两次幂,等于 4 ,所以 c4=a1+a2+a3+a4(由四向前数四个数的和)。

C数组C[i] = a[i – 2^k + 1] + … + a[i] k为二进制下某尾0的个数。

Lowbit(): 返回的就是 2^k的值。

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

Add(): 更新树状数组;

void Add(int i,int t)
{
    while(i<=n)
    {
        c[i]+=t;
        i+=lowbit(i);
    }
}

int sum() 就是把C数组全部加起来就好了,会得到一个下标为i前缀和

int Sum(int i)
{
    int sum=0;
    while(i)
    {
        sum+=c[i];
        i-=lowbit(i);
    }
    return sum;
}

Change() 当有元素变更的时候,树状数组的优势就特别大。

void Change(int i,int num)
{
    //这个往往为变成数据范围+7,反正超出就行,因为你改变一个值以后,后面的有些c数组会改变,为什么说有些呢,结合c数组的特性,那个运算,我们可以发现什么?留给巨巨们思考了
     while(i<=n)
{
        c[i]+=x;
        i+=lowbit(i);
    }
}

一个小插曲(也可以解释前面的为什么):
插:错误往往都是很能说明问题的,但是你首先得明确问题,才能办好事。

要求:
实现在一个区间都加上x,然后计算区间值。
插:如果巨巨看出为什么错的话,那后面的解释就当看看过吧,或者再给我说说,我也好更加理解,谢谢~

想法(错误的):
弱的思路就是:lowbit就是他要加上的区间宽度,那么乘以要加的值再加上前面加的就是所有要加的。。。。。
代码:

void Add_duan(int i,LL t,int w)     //i是区间起始点,t是要加的值,w是末端
{
    LL temp=t;
    LL q;
    while(i<=w)
    {
        q=t;
        C[i]+=t;
        t=lowbit(i)*temp+t;
        i+=lowbit(i);
    }
    while(i<=n)
    {
        C[i]+=q;
        i+=lowbit(i);
    }
}

然后就不行啊!!!
我只能解释一下(似乎解释不全):
树状数组英文名:Binary Indexeds Tree,再英译汉一下,二进制索引树。
二进制索引的特性,当我要把2-4这个范围内的数+1的话,我跑我刚刚的程序,直接把3略过去了,3只能吃2的,3没有被处理。
类同…4-8-16-32区间内的

GG
而且这个BIT为什么复杂度在log(n);也是这个道理啊,所以BIT就是只能用来计算前缀和的。。。
OK,巨巨请多多建议一下。讲的也很挫…

区间更新正解(引我大哥的博文):
http://www.wonter.net/?p=335

几道基础题

POJ2352;

题意:
计算每个等级的星星有多少颗,0~n-1个等级,每个星星的等级=每个星星左下角星星的数量
思路:
输入本来就是给出以y为增序,即y已经保证后面的肯定比前面大于等于,那么直接处理x,每次计算x的前缀和,然后更新,用个level数组记录就好了;
code……….

//#include<bits/stdc++.h>
#include<cstdio>
#include<iostream>
#include<math.h>
#include<string.h>
#include<algorithm>
using namespace std;
typedef long long LL;
typedef unsigned long long ULL;
const double eps=1e-5;
const double pi=acos(-1.0);
const int mod=1e8+7;
const int INF=0x3f3f3f3f;

const int N=15005;
const int M=32005;

int le[N];
int c[M];
int n;

void add(int i,int t)
{
    while(i<=M)
    {
        c[i]+=t;
        i+=i&(-i);
    }
}
int Sum(int i)
{
    int ans=0;
    while(i>0)      //如果是0的话,进过位运算只能是0,0-0还是0,所以要避免
    {
        ans+=c[i];
        i-=i&(-i);
    }
    return ans;
}

int main()
{
    scanf("%d",&n);
    memset(c,0,sizeof(c));
    memset(le,0,sizeof(le));
    int x,y;
    for(int i=1;i<=n;i++)
    {
        scanf("%d%d",&x,&y);
        le[Sum(x+1)]++;     //让标志点全部往右移一个,而更新的所以也要改,防止0,会陷入死循环
        add(x+1,1);
    }
    for(int i=0;i<n;i++)
    printf("%d\n",le[i]);
    return 0;
}

POJ 2481;

题意:
计算每个区间有多少个包含他的区间。
思路:
树状数组只能计算前缀和,那么前缀和首先得有前缀啊。我们要计算一个区间有多少个包含他,那么所以越小的区间要越晚处理。那么我们按照y从大到小排序,x从小到大,然后就像慢慢逼近小区间一样,计算x的前缀和就好了。
code…………(C++过,G++ t掉了…)

//#include<bits/stdc++.h>
#include<cstdio>
#include<iostream>
#include<math.h>
#include<string.h>
#include<algorithm>
using namespace std;
typedef long long LL;
typedef unsigned long long ULL;
const double eps=1e-5;
const double pi=acos(-1.0);
const int mod=1e8+7;
const int INF=0x3f3f3f3f;

const int N=1e5+10;

int c[N];
int cnt[N];
struct asd{
    int s,e;
    int pos;
};
asd q[N];
int n;
bool cmp(asd x,asd y)
{
    if(x.e==y.e)
        return x.s<y.s;
    return x.e>y.e;
}
int lowbit(int i)
{
    return i&(-i);
}
int SUM(int i)
{
    int s=0;
    while(i>0)
    {
        s+=c[i];
        i-=lowbit(i);
    }
    return s;
}
void add(int i,int t)
{
    while(i<=n)
    {
        c[i]+=t;
        i+=lowbit(i);
    }
}

int main()
{
    while(scanf("%d",&n)&&n)
    {
        for(int i=1;i<=n;i++)
        {
            scanf("%d%d",&q[i].s,&q[i].e);
            q[i].pos=i;
        }
        sort(q+1,q+n+1,cmp);

        memset(cnt,0,sizeof(cnt));
        memset(c,0,sizeof(c));
        cnt[q[1].pos]=0;
        add(q[1].s+1,1);
        for(int i=2;i<=n;i++)
        {
            if(q[i].s==q[i-1].s&&q[i].e==q[i-1].e)
                cnt[q[i].pos]=cnt[q[i-1].pos];
            else
                cnt[q[i].pos]=SUM(q[i].s+1);
            add(q[i].s+1,1);
        }
        for(int i=1;i<=n;i++)
        {
            if(i!=1)
                printf(" ");
            printf("%d",cnt[i]);
        }
        puts("");
    }
    return 0;
}

POJ1990

题意:
计算所有两两点的距离*max(v[i],v[j]);
思路:
我们还是先按照v值按照降序排序,那么对于一个点,这个v是确定的。这题计算距离,利用前缀和。我们可以发现,一个点左边右边都有可能有点,我们计算距离=xj(右边)-x或者=x-xj(左边);那么我们对左边的点累加起来就是=n1*x-(sum)xj;n1为左边有多少个点,(sum)xj为左边的点的坐标之和。右边同理可得。那么刚好,这个左边右边的点数利用树状数组可以实现,不就是个前缀和,最多加加减减。距离也是。
code………………

//#include<bits/stdc++.h>
#include<cstdio>
#include<iostream>
#include<math.h>
#include<string.h>
#include<algorithm>
using namespace std;
typedef long long LL;
typedef unsigned long long ULL;
const double eps=1e-5;
const double pi=acos(-1.0);
const int mod=1e8+7;
const int INF=0x3f3f3f3f;

const int N=20025;
LL dis[N];
LL num[N];
struct asd{
    LL v;
    LL x;
};
int n;
asd q[N];
bool cmp(asd z1,asd z2)
{
    return z1.v<z2.v;
}

LL Sum(LL i,LL *x)
{
    LL ans=0;
    while(i>0)
    {
        ans+=x[i];
        i-=i&(-i);
    }
    return ans;
}
void add(LL i,LL t,LL *x)
{
    while(i<=N-10)
    {
        x[i]+=t;
        i+=i&(-i);
    }
}
int main()
{
    scanf("%d",&n);
    for(int i=0;i<n;i++)
        scanf("%lld%lld",&q[i].v,&q[i].x);
    sort(q,q+n,cmp);
    memset(num,0,sizeof(num));
    memset(dis,0,sizeof(dis));
    LL ans=0;
    for(int i=0;i<n;i++)
    {
        LL x=q[i].x;
        LL le=Sum(x-1,num);
        LL ri=Sum(N-10,num)-Sum(x,num);
        ans+=q[i].v*(le*x-Sum(x-1,dis)+Sum(N-10,dis)-Sum(x,dis)-ri*x);
        add(x,1,num);
        add(x,x,dis);
    }
    printf("%lld\n",ans);
    return 0;
}

POJ2299

题意:
求一个序列的逆序数。
思路:
这个网上讲的挺好的。多了一个离散化操作。还有就是树状数组就是可以计算一个前缀和。逆序数=位置-前面比他小的;(话说我们应该更好理解就是后面有多少比他大的吧,其实一样,都是前缀和可以解决,具体看别的博文吧,弱不想说这个了。。。)
code…………

//#include<bits/stdc++.h>
#include<cstdio>
#include<iostream>
#include<math.h>
#include<string.h>
#include<algorithm>
using namespace std;
typedef long long LL;
typedef unsigned long long ULL;
const double eps=1e-5;
const double pi=acos(-1.0);
const int mod=1e8+7;
const int INF=0x3f3f3f3f;

/*

利用的就是树状数组的单点更新和区间求值,但是逆序嘛,也是可以求后面有多少比他大的,
枚举数组就可以依次将值塞进数组,然后瞎搞一下就好了。
逆序:
其中 i 为当前已经插入的数的个数,
getsum( aa[i] )为比 aa[i] 小的数的个数,
i- sum( aa[i] ) 即比 aa[i] 大的个数, 即逆序的个数

*/

const int N=1e5+7;
struct asd{
    int v,pos;
};
asd q[N*5];
int ans[N*5];
int c[N*5];
int n;

bool cmp(asd x,asd y)
{
    return x.v<y.v;
}
int getsum(int i)
{
    int sum=0;
    while(i>=1)
    {
        sum+=c[i];
        i-=i&(-i);
    }
    return sum;
}

void update(int i,int t)
{
    while(i<=n)
    {
        c[i]+=t;
        i+=i&(-i);
    }
}

int main()
{
    while(~scanf("%d",&n)&&n)
    {
        memset(ans,0,sizeof(ans));
        for(int i=1;i<=n;i++)
        {
            scanf("%d",&q[i].v);
            q[i].pos=i;
        }
        sort(q+1,q+n+1,cmp);
        for(int i=1;i<=n;i++)
            ans[q[i].pos]=i;


        memset(c,0,sizeof(c));

        LL sum=0;
        for(int i=1;i<=n;i++)
        {
            update(ans[i],1);
            sum+=i-getsum(ans[i]);
        }
        printf("%lld\n",sum);
    }
    return 0;
}

POJ3067

直接code……..如果刷完了上面,这题分分钟的事情…加油!巨巨这个时候应该好好去刷难题了!刷难题才能有提升!!!

//#include<bits/stdc++.h>
#include<cstdio>
#include<iostream>
#include<math.h>
#include<string.h>
#include<algorithm>
using namespace std;
typedef long long LL;
typedef unsigned long long ULL;
const double eps=1e-5;
const double pi=acos(-1.0);
const int mod=1e8+7;
const int INF=0x3f3f3f3f;

const int N=1e3+10;
int c[N];
int m,n,k;

struct asd{
    int x,y;
};
asd q[N*N];
bool cmp(asd a,asd b)
{
    if(a.x==b.x)return a.y<=b.y;
    else return a.x<b.x;
}

int Sum(int i)
{
    int ans=0;
    while(i>0)
    {
        ans+=c[i];
        i-=i&(-i);
    }
    return ans;
}

void add(int i,int t)
{
    while(i<=N-2)
    {
        c[i]+=t;
        i+=i&(-i);
    }
}

int main()
{
    int t;
    int cas=1;
    scanf("%d",&t);
    while(t--)
    {
        scanf("%d%d%d",&n,&m,&k);
        for(int i=0;i<k;i++)
            scanf("%d%d",&q[i].x,&q[i].y);

        sort(q,q+k,cmp);
        memset(c,0,sizeof(c));
        LL ans=0;
        for(int i=0;i<k;i++)
        {
            add(q[i].y,1);
            ans+=Sum(m)-Sum(q[i].y);
        }
        printf("Test case %d: %lld\n",cas++,ans);
    }
    return 0;
}
posted @ 2016-08-02 11:09  see_you_later  阅读(214)  评论(0编辑  收藏  举报