记得以前就看过树状数组,好像因为并不能看懂,最近学习中偶得一文,讲解的十分清楚:
贴个链接:http://blog.csdn.net/int64ago/article/details/7429868
下面直接引用大牛所写的东西:
lowbit(k)就是把k的二进制的高位1全部清空,只留下最低位的1,比如10的二进制是1010,则lowbit(k)=lowbit(1010)=0010(2进制)
lowbit(1111)=1
lowbit(1011)=1
上面那么多文字说lowbit,还没说它的用处呢,它就是为了联系a数组和c数组的!ck表示从ak开始往左连续求lowbit(k)个数的和,比如c[0110]=a[0110]+a[0101],就是从110开始计算了0010个数的和,因为lowbit(0110)=0010,可以看到其实只有低位的1起作用,因为很显然可以写出c[0010]=a[0010]+a[0001],这就为什么我们任何数都只关心它的lowbit,因为高位不起作用(基于我们的二分规则它必须如此!),除非除了高位其余位都是0,这时本身就是lowbit。
既然关系建立好了,看看如何实现a某一个位置数据跟改的,她不会直接改的(开始就说了,a根本不存在),她每次改其实都要维护c数组应有的性质,因为后面求和要用到。而维护也很简单,比如更改了a[0011],我们接着要修改c[0011],c[0100],c[1000],这是很容易从图上看出来的,但是你可能会问,他们之间有申明必然联系吗?每次求解总不能总要拿图来看吧?其实从0011——>0100——>1000的变化都是进行“去尾”操作,又是自己造的词–”,我来解释下,就是把尾部应该去掉的1都去掉转而换到更高位的1,记住每次变换都要有一个高位的1产生,所以0100是不能变换到0101的,因为没有新的高位1产生,这个变换过程恰好是可以借助我们的lowbit进行的,k +=lowbit(k)。
好吧,现在更新的次序都有了,可能又会产生新的疑问了:为什么它非要是这种关系啊?这就要追究到之前我们说c8可以看作a1~a8的左半边和+右半边和……的内容了,为什么c[0011]会影响到c[0100]而不会影响到c[0101],这就是之前说的c[0100]的求解实际上是这样分段的区间 c[0001]~c[0001] 和区间c[0011]~c[0011]的和,数字太小,可能这样不太理解,在比如c[0100]会影响c[1000],为什么呢?因为c[1000]可以看作0001~0100的和加上0101~1000的和,但是0101位置的数变化并会直接作用于c[1000],因为它的尾部1不能一下在跳两级在产生两次高位1,是通过c[0110]间接影响的,但是,c[0100]却可以跳一级产生一次高位1。
可能上面说的你比较绕了,那么此时你只需注意:c的构成性质(其实是分组性质)决定了c[0011]只会直接影响c[0100],而c[0100]只会直接影响[1000],而下表之间的关系恰好是也必须是k +=lowbit(k)
先做了以前用线段树做的入门题,试着改用树状数组做,倒是一时没想出来如何去处理端点 l 和 r;
贴个代码:
hdu 1556
using namespace std;
const int MAXN=100005;
int c[MAXN];
int n;
int lowbit(int x){ //把二进制中所有的高位1 全部都去掉,只留下最低位的1
return x&(-x);
}
void add(int i,int val){
while(i<=n){
c[i]+=val;
i+=lowbit(i);
}
}
int sum(int i){ //求1~n 的区间和, 但是这其中有很多个线段,每lowbit一次就是跳到下一个区间
int s=0;
while(i>0){
s+=c[i];
i-=lowbit(i);
}
return s;
}
int main(){
//freopen("1.txt","r",stdin);
int a,b;
while(~scanf("%d",&n)&& n){
memset(c,0,sizeof(c));
for(int i=0;i<n;i++){
scanf("%d%d",&a,&b);
add(a,1);
add(b+1,-1);
// for(int j=1;j<=n;j++)
// printf("%d ",c[j]);
// printf("\n");
}
for(int i=1;i<n;i++)
printf("%d ",sum(i));
printf("%d\n",sum(n));
}
return 0;
}
水题:
hdu…..1541
这题要记录的是,思维比较有用的一种树状数组:
我的想法:每一颗星星都有x,y。 我想将所有的星星(记录在A里面)按x 从小到大排,然后用一个X数组记录所有星星的y(假设题意中星星的y 不是从小到大), 然后从遍历A,对每一个A,先用lower_bound找到A[i].y在X中的位置pos,对应的树状数组sum(pos) 就是A[i].i 的答案。
分析: 因为x必然是按照从小到大的顺序排列,更新A[i].y位置pos 很巧妙地达到了我们的目的。
#include<iostream>
#include<stdio.h>
#include<string.h>
#include<math.h>
#include<algorithm>
#include<stdlib.h>
#include<queue>
#include<stack>
#include<map>
#include<vector>
#define mem(a) memset(a,0,sizeof(a))
#define INF 0x7fffffff //INT_MAX
#define inf 0x3f3f3f3f //
const double PI = acos(-1.0);
const double e = exp(1.0);
template<class T> T gcd(T a, T b) { return b ? gcd(b, a % b) : a; }
template<class T> T lcm(T a, T b) { return a / gcd(a, b) * b; }
bool cmpbig(int a,int b){return a>b;}
bool cmpsmall(int a,int b){return a<b;}
using namespace std;
struct segment{
int x, y, i;
} A[200000];
bool cmp(segment a,segment b){
if(a.x==b.x)
return a.y<b.y;
else
return a.x<b.x;
}
int N;
int ans[200000];
int X[200000], NX;
int c[200001];
void add(int i, int v){
for(; i<=N; i+=i&-i)
c[i]+=v;
}
int sum(int x){
int ret=0;
for(; x>0; x-=x&-x)
ret+=c[x];
return ret;
}
int flag[200000];
int main()
{
freopen("1.txt","r",stdin);
while(~scanf("%d", &N)){
mem(c);
for(int i=1; i<=N; i++){
scanf("%d %d",&A[i].x,&A[i].y);
A[i].i=i;
X[i]=A[i].y;
}
sort(X+1,X+N);
sort(A+1,A+1+N,cmp);
mem(ans);
for(int i=1;i<=N;i++){
int pos=lower_bound(X+1,X+N+1,A[i].y)-X;
// printf("pos=%d\n",pos);
ans[A[i].i]=sum(pos);
add(pos,1);
}
mem(flag);
for(int i=1;i<=N;i++){
// printf("%d\n",ans[i]);
flag[ans[i]]++;
}
for(int i=0;i<N;i++)
printf("%d\n",flag[i]);
}
return 0;
}
类似的题目:
经典树状数组 : Educational Codeforces Round 10 D - Nested Segments
题意:给你n个线段(区间),然后依次输出每一个线段 包含了多少个线段 : 比如 (1,10)包含(2,8)但是不包含(5,11)
仔细想想会发现,和上面那个题的处理方法其实是一样的:我们只要先把每个线段放入A里面, 然后按 l从大到小排列, 然后将 所有的A[i].r 放进X里面,X也sort一下,r是按从小到大排列
然后遍历A,对每一个A[i],找到r 在X中的位置pos,接着对于这一个i 的ans就是树状数组的sum(pos);
#include<iostream>
#include<stdio.h>
#include<string.h>
#include<math.h>
#include<algorithm>
#include<stdlib.h>
#include<queue>
#include<stack>
#include<map>
#include<vector>
#define mem(a) memset(a,0,sizeof(a))
#define INF 0x7fffffff //INT_MAX
#define inf 0x3f3f3f3f //
const double PI = acos(-1.0);
const double e = exp(1.0);
template<class T> T gcd(T a, T b) { return b ? gcd(b, a % b) : a; }
template<class T> T lcm(T a, T b) { return a / gcd(a, b) * b; }
bool cmpbig(int a,int b){return a>b;}
bool cmpsmall(int a,int b){return a<b;}
using namespace std;
struct segment{
int l, r, i;
} A[200005];
bool cmp(segment a,segment b){
if(a.l==b.l)
return a.r<b.r;
else
return a.l>b.l;
}
int N;
int ans[200005];
int X[200005], NX;
int c[200005];
void add(int i, int v){
for(; i<=N; i+=i&-i)
c[i]+=v;
}
int sum(int x){
int ret=0;
for(; x>0; x-=x&-x)
ret+=c[x];
return ret;
}
int main()
{
//freopen("1.txt","r",stdin);
while(~scanf("%d", &N)){
mem(c);
for(int i=1; i<=N; i++){
scanf("%d %d",&A[i].l,&A[i].r);
A[i].i=i;
X[i]=A[i].r;
}
sort(X+1,X+N+1);
sort(A+1,A+1+N,cmp);
mem(ans);
for(int i=1;i<=N;i++){
int pos=lower_bound(X+1,X+N+1,A[i].r)-X ;
ans[A[i].i]=sum(pos);
add(pos,1);
}
for(int i=1;i<=N;i++){
printf("%d\n",ans[i]);
}
}
return 0;
}