树状数组萌新讲解+基础习题【一点一滴】
来源:互联网 发布:御彩轩计划软件删除 编辑:程序博客网 时间: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;}
- 树状数组萌新讲解+基础习题【一点一滴】
- 树状数组习题总结
- 树状数组讲解
- 树状数组讲解
- 数据结构-树状数组讲解
- 树状数组粗略讲解
- 知识点:树状数组讲解
- 树状数组经典讲解
- 8.26树状数组讲解
- 树状数组讲解
- 树状数组习题:棋子等级
- 树状数组 讲解(转载)
- 树状数组(讲解+模版)
- 集训8.21树状数组讲解
- 基础树状数组
- 树状数组基础
- 基础树状数组
- 树状数组基础
- 用vim打开文件每行出现^M怎么办-
- chef
- Android:CourseTableLayout — 好用的Android自动生成课程表的自定义控件
- Spring service本类中方法调用另一个方法事务不生效问题
- 深入剖析android消息机制
- 树状数组萌新讲解+基础习题【一点一滴】
- 【好文推荐】Spring中ApplicationContext的事件机制
- java redis使用之利用jedis实现redis消息队列
- MyBatis学习总结(一)——MyBatis快速入门
- 用这四种套路更新缓存,你会少走很多弯路!
- 当虚拟机的客户端与服务端连接时候,虚拟机卡死了
- HDU 3339 In Action (dijkskra+01背包)
- 最近公共祖先LCA问题(转)
- NYOJ27 水池数目 BFS