【树状数组】知识点讲解+例题x1



以下讲解很有用,via:https://www.cnblogs.com/hsd-/p/6139376.html

【区间查询】
ok 下面利用C[i]数组,求 A数组中前i项的和 
举个例子 i=7;
sum[7]=A[1]+A[2]+A[3]+A[4]+A[5]+A[6]+A[7] ;   前i项和
C[4]= A[1]+ A[2]+A[3]+A[4] ;    C[6]=A[5]+A[6];    C[7]=A[7];
可以推出:    sum[7]=C[4]+C[6]+C[7];
序号写为二进制: sum[(111)]= C[(100)]+C[(110)]+C[(111)];
 
再举个例子 i=5
sum[7]=A[1]+A[2]+A[3]+A[4]+A[5] ;   前i项和
C[4]= A[1]+ A[2]+A[3]+A[4] ;   C[5]=A[5];
可以推出:    sum[5]=C[4]+C[5];
序号写为二进制: sum[(101)]= C[(100)]+C[(101)];
 
细细观察二进制 树状数组追其根本就是二进制的应用
int sum(int i)
{
	int res=0;
	while(i>0)
	{
		res+=c[i];
		i-=i&(-i);
	}
	return res;
}


对于i=7 进行演示 
                                  7(111)           ans+=C[7]
lowbit(7)=001  7- lowbit(7)=6(110)    ans+=C[6]
lowbit(6)=010  6-lowbit(6)=4(100)    ans+=C[4]
lowbit(4)=100  4-lowbit(4)=0(000)    
对于i=5 进行演示 
                                  5(101)            ans+=C[5]
lowbit(5)=001  5- lowbit(5)=4(100)    ans+=C[4]
lowbit(4)=100  4-lowbit(4)=0(000)    
 


【单点更新】
当我们修改A[]数组中的某一个值时  应当如何更新C[]数组呢?
回想一下 区间查询的过程,再看一下上文中列出的图
 
void add(int i,int v) 
{
	while(i<=n)
	{
		c[i]+=v;
		i+=i&(-i);
	}
} 

 
如图: 
当更新A[1]时  需要向上更新C[1] ,C[2],C[4],C[8]   
(看图看看这四个C点,会发现其实i+lowbit(i)有一种层次嵌套A[i]的关系,其他就都与你A[i]无关了。)
                     C[1],   C[2],    C[4],     C[8]
写为二进制   C[(001)],C[(010)],C[(100)],C[(1000)]
                                      1(001)        C[1]+=A[1]
lowbit(1)=001 1+lowbit(1)=2(010)      C[2]+=A[1]
lowbit(2)=010 2+lowbit(2)=4(100)     C[4] +=A[1]
lowbit(4)=100 4+lowbit(4)=8(1000)   C[8] +=A[1]

其实你发现没有,A[i]单点还是单点,而C[i]的加入使得区间查询就很方便!

代码中全然没有A数组的存在,完全由C数组来解决问题。

(理解误区:C数组就是A数组的前缀和?错,区间查询才是求前缀和,C[i]是以一种规定好的这个数据结构里的方式来跟A以及其他C建立联系【看图】)





例题【棋子等级】




大致思路

用上树状数组的“区间查询”和“单点更新”的模板题。

这道题暴力肯定是能给点分的,但是也是很少的分,因为数据量一大你去遍历肯定要超时。而用上树状数组这个数据结构又可以成功“优化”。

重点在于如何转化成树状数组问题的思路

我们可以把棋盘的每一个点都看成A[i],默认为0,如果有个棋子则A[i]=1。

左下方肯定是要x和y都比你小的,而题目说了输入的数据按y升序排列,所以结合树状数组“区间查询”的一个流程,我们可以无视y,只看前面的比你的x值小的点的个数啊!即:求A[1]+A[2]+...+A[x] (此时的x因为默认为0,所以不影响题意)

当然,我是边放棋子边立刻计算其等级的,所以统计之后也要“更新单点”,这是为之后的点的统计做准备(即随时更新C[i],即用即有)。

细节注意:树状数组的算法里都是以A[1]和C[1]打头的,所以这个题里面x从0开始,我们就把它视作从1开始,只要把输入的x加1即可。


AC代码

#include<cstdio>
#include<cstring>
using namespace std;
int c[100001];
int rank[100001];
int n;
int getsum(int x){
    int sum=0;
    for(;x;x-=x&(-x))
    {
        sum+=c[x];
    }
    return sum;
}
void renew(int i,int v){
    for(;i<=100000;i+=i&(-i)){
        c[i]+=v;
    }
}
int main(){
    int x,y,number;
    memset(c,0,sizeof(c));
    memset(rank,0,sizeof(rank));
    scanf("%d",&n);
    for(int i=0;i<n;i++){
        scanf("%d%d",&x,&y);
        x++;
        number=getsum(x);
        rank[number]++;
        renew(x,1);
    }
    for(int i=0;i<n;i++){
        printf("%d\n",rank[i]);	
    }
    return 0;
}

猜你喜欢

转载自blog.csdn.net/m0_38033475/article/details/80319086
x1
今日推荐