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

来源:互联网 发布:御彩轩计划软件删除 编辑:程序博客网 时间:2024/04/24 21:30

树状数组基础篇

树状数组讲点

中文名:树状数组
英文名: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;}
1 0
原创粉丝点击