树状数组基础篇
树状数组讲点
中文名:树状数组
英文名: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
",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
",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
",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
",cas++,ans);
}
return 0;
}