树状数组C++版Word下载.docx
- 文档编号:18072251
- 上传时间:2022-12-13
- 格式:DOCX
- 页数:20
- 大小:88.49KB
树状数组C++版Word下载.docx
《树状数组C++版Word下载.docx》由会员分享,可在线阅读,更多相关《树状数组C++版Word下载.docx(20页珍藏版)》请在冰豆网上搜索。
00000110
7-->
00000111
8-->
00001000
1的位置其实从我画的满二叉树中就可以看出来。
但是这与C[]有什么关系呢?
接下来的这部分内容很重要:
在满二叉树中,
以1结尾的那些结点(C[1],C[3],C[5],C[7]),其叶子数有1个,所以这些结点C[i]代表区间范围为1的元素和;
以10结尾的那些结点(C[2],C[6]),其叶子数为2个,所以这些结点C[i]代表区间范围为2的元素和;
以100结尾的那些结点(C[4]),其叶子数为4个,所以这些结点C[i]代表区间范围为4的元素和;
以1000结尾的那些结点(C[8]),其叶子数为8个,所以这些结点C[i]代表区间范围为8的元素和。
扩展到一般情况:
i的二进制中的从右往左数有连续的x个“0”,那么拥有2^x个叶子,为序列A[]中的第i-2^x+1到第i个元素的和。
终于,我们得到了一个C[i]的具体定义:
C[i]=A[i-2^x+1]+…+A[i],其中x为i的二进制中的从右往左数有连续“0”的个数。
第03讲利用树状数组求前i个元素的和S[i]
理解了C[i]后,前i个元素的和S[i]就很容易实现。
从C[i]的定义出发:
我们可以知道:
C[i]是肯定包括A[i]的,那么:
S[i]=C[i]+C[i-2^x]+…
也许上面这个公式太抽象了,因为有省略号,我们拿一个具体的实例来看:
S[7]=C[7]+C[6]+C[4]
因为C[7]=A[7],C[6]=A[6]+A[5],C[4]=A[4]+A[3]+A[2]+A[1],所以S[7]=C[7]+C[6]+C[4]
(1)i=7,求得x=0,那么我们求得了A[7];
(2)i=i-2^x=6,求得x=1,那么求得了A[6]+A[5];
(3)i=i-2^x=4,求得x=2,那么求得了A[4]+A[3]+A[2]+A[1]。
讲到这里其实有点难度,因为S[i]的求法,如果要讲清楚,那么得写太多的东西了。
所以不理解的同学,再反复多看几遍。
从
(1)
(2)(3)这3步可以知道,求S[i]就是一个累加的过程,如果将2^x求出来了,那么这个过程用C++实现就没什么难度。
现在直接告诉你结论:
2^x=i&
(-i)
证明:
设A’为A的二进制反码,i的二进制表示成A1B,其中A不管,B为全0序列。
那么-i=A’0B’+1。
由于B为全0序列,那么B’就是全1序列,所以-i=A’1B,所以:
i&
(-i)=A1B&
A’1B=1B,即2^x的值。
所以根据
(1)
(2)(3)的过程我们可以写出如下的函数:
intSum(inti)//返回前i个元素和
{
ints=0;
while(i>
0)
{
s+=C[i];
i-=i&
(-i);
}
returns;
}
第04讲更新C[]
正如第01讲提到的小石块问题,如果数组A[i]被更新了怎么办?
那么如何改动C[]?
如果改动C[]也需要O(n)的时间复杂度,那么树状数组就没有任何优势。
所以树状数组在改动C[]上面的时间效率为O(logn),为什么呢?
因为改动A[i]只需要改动部分的C[]。
这一点从第02讲的图中就可以看出来:
如上图:
假如A[3]=3,接着A[3]+=1,那么哪些C[]需要改变呢?
答案从图中就可以得出:
C[3],C[4],C[8]。
因为这些值和A[3]是有联系的,他们用树的关系描述就是:
C[3],C[4],C[8]是A[3]的祖先。
那么怎么知道那些C[]需要变化呢?
我们来看“A”这个结点。
这个“A”结点非常的重要,因为他体现了一个关系:
A的叶子数为C[3]的2倍。
因为“A”的左子树和右子树的叶子数是相同的。
因为2^x代表的就是叶子数,所以C[3]的父亲是A,A的父亲是C[i+2^0],即C[3]改变,那么C[3+2^0]也改变。
我们再来看看“B”这个结点。
B结点的叶子数为2倍的C[6]的叶子数。
所以B和C[6+2^1]在同一列,所以C[6]改变,C[6+2^1]也改变。
推广到一般情况就是:
如果A[i]发生改变,那么C[i]发生改变,C[i]的父亲C[i+2^x]也发生改变。
这一行的迭代过程,我们可以写出当A[i]发生改变时,C[]的更新函数为:
voidUpdate(inti,intvalue)//A[i]的改变值为value
while(i<
=n)
C[i]+=value;
i+=i&
第05讲一维树状数组的应用举例
废了4讲的话,我们终于把一维树状数组的2个不到5行的代码给搞定了。
现在要正式投入到应用当中。
Codevs1080线段树练习
题目描述Description
一行N个方格,开始每个格子里都有一个整数。
现在动态地提出一些问题和修改:
提问的形式是求某一个特定的子区间[a,b]中所有元素的和;
修改的规则是指定某一个格子x,加上或者减去一个特定的值A。
现在要求你能对每个提问作出正确的回答。
1≤N<
100000,,提问和修改的总数m<
10000条。
输入描述InputDescription
输入文件第一行为一个整数N,接下来是n行n个整数,表示格子中原来的整数。
接下一个正整数m,再接下来有m行,表示m个询问,第一个整数表示询问代号,询问代号1表示增加,后面的两个数x和A表示给位置X上的数值增加A,询问代号2表示区间求和,后面两个整数表示a和b,表示要求[a,b]之间的区间和。
输出描述OutputDescription
共m行,每个整数
样例输入SampleInput
6
4
5
6
2
1
3
4
135
214
119
226
样例输出SampleOutput
22
数据范围及提示DataSize&
Hint
1≤N≤100000,m≤10000。
参考程序:
#include<
iostream>
cstdlib>
cstdio>
cstring>
algorithm>
cmath>
usingnamespacestd;
intn,k,m,s[100005],c[100005];
intlowbit(intx)
returnx&
-x;
voidchang(intx,inty)
while(x<
c[x]+=y;
x+=lowbit(x);
intsumsol(intL,intR)
while(R>
s+=c[R];
R-=lowbit(R);
intmain()
memset(c,0,sizeof(c));
cin>
>
n;
for(inti=1;
i<
=n;
i++)
cin>
s[i];
k=s[i];
chang(i,k);
m;
=m;
intr1,r2,r3;
r1>
r2>
r3;
if(r1==1)chang(r2,r3);
if(r1==2)printf("
%d\n"
sumsol(1,r3)-sumsol(1,r2-1));
return0;
Poj2299
树状数组求逆序对的个数
给定n个数,要求这些数构成的逆序对的个数。
1.解释为什么要有离散的这么一个过程?
刚开始以为999.999.999这么一个数字,对于int存储类型来说是足够了。
还有只有500000个数字,何必要离散化呢?
刚开始一直想不通,后来明白了,后面在运用树状数组操作的时候,
用到的树状数组C[i]是建立在一个有点像位存储的数组的基础之上的,
不是单纯的建立在输入数组之上。
比如输入一个91054,那么C[i]树状数组的建立是在,
数据:
91054p[i].val
编号:
12345p[i].oder=i*************
sort
01459
32541
顺序:
12345
a[p[i].编号]=顺序号;
**********************
a[3]=1<
--0;
a[2]=2<
--1;
a[5]=3<
--4;
a[4]=4<
--5;
a[1]=5<
--9;
a[]={52143}
新号:
值:
下标0123456789
数组1100110001
现在由于999999999这个数字相对于500000这个数字来说是很大的,
所以如果用数组位存储的话,那么需要999999999的空间来存储输入的数据。
这样是很浪费空间的,题目也是不允许的,所以这里想通过离散化操作,
使得离散化的结果可以更加的密集。
简言之就是开一个大小为这些数的最大值的树状数组
2.怎么对这个输入的数组进行离散操作?
离散化是一种常用的技巧,有时数据范围太大,可以用来放缩到我们能处理的范围;
因为其中需排序的数的范围0---999999999;
显然数组不肯能这么大;
而N的最大范围是500000;
故给出的数一定可以与1.。
。
N建立一个一一映射;
(1)当然用map可以建立,效率可能低点;
(2)这里用一个结构体
structNode
intval,pos;
}p[510000];
和一个数组a[510000];
其中val就是原输入的值,pos是下标;
然后对结构体按val从小到大排序;
此时,val和结构体的下标就是一个一一对应关系,
而且满足原来的大小关系;
for(i=1;
=N;
a[p[i].pos]=i;
然后a数组就存储了原来所有的大小信息;
比如91054-------离散后aa数组
就是52143;
具体的过程可以自己用笔写写就好了。
3.离散之后,怎么使用离散后的结果数组来进行树状数组操作,计算出逆序数?
如果数据不是很大,可以一个个插入到树状数组中,
每插入一个数,统计比他小的数的个数,
对应的逆序为i-sum(a[i]),
其中i为当前已经插入的数的个数,
sum(a[i])为比a[i]小的数的个数,
i-sum(a[i])即比a[i]大的个数,即逆序的个数
但如果数据比较大,就必须采用离散化方法
假设输入的数组是91054,离散后的结果a[]={5,2,1,4,3};
在离散结果中间结果的基础上,那么其计算逆序数的过程是这么一个过程。
1.输入5,调用add(5,1),把第5位设置为1
00001
计算1-5上比5小的数字存在么?
这里用到了树状数组的sum(5)=1操作,
现在用输入的下标1-sum(5)=0就可以得到对于5的逆序数为0。
2.输入2,调用add(2,1),把第2位设置为1
01001
计算1-2上比2小的数字存在么?
这里用到了树状数组的sum
(2)=1操作,
现在用输入的下标2-sum
(2)=1就可以得到对于2的逆序数为1。
3.输入1,调用add(1,1),把第1位设置为1
11001
计算1-1上比1小的数字存在么?
这里用到了树状数组的sum
(1)=1操作,
现在用输入的下标3-sum
(1)=2就可以得到对于1的逆序数为2。
4.输入4,调用add(4,1),把第5位设置为1
11011
计算1-4上比4小的数字存在么?
这里用到了树状数组的sum(4)=3操作,
现在用输入的下标4-sum(4)=1就可以得到对于4的逆序数为1。
5.输入3,调用add(3,1),把第3位设置为1
11111
计算1-3上比3小的数字存在么?
这里用到了树状数组的sum(3)=3操作,
现在用输入的下标5-sum(3)=2就可以得到对于3的逆序数为2。
6.0+1+2+1+2=6这就是最后的逆序数
分析一下时间复杂度,首先用到快速排序,时间复杂度为O(NlogN),
后面是循环插入每一个数字,每次插入一个数字,分别调用一次add()和sum()
外循环N,add()和sum()时间O(logN)=>
时间复杂度还是O(NlogN)
#include<
constintN=500005;
structNode
intval;
intpos;
};
Nodenode[N];
intc[N],reflect[N],n;
boolcmp(constNode&
a,constNode&
b)
returna.val<
b.val;
returnx&
(-x);
voidupdate(intx)
while(x<
=n)
c[x]+=1;
x+=lowbit(x);
intgetsum(intx)
intsum=0;
while(x>
0)
sum+=c[x];
x-=lowbit(x);
returnsum;
while(scanf("
%d"
&
n)!
=EOF&
&
n)
for(inti=1;
i<
=n;
++i)
{
scanf("
node[i].val);
node[i].pos=i;
}
sort(node+1,node+n+1,cmp);
//排序
for(inti=1;
i++)printf("
node[i].pos);
++i)reflect[node[i].pos]=i;
//离散化
++i)c[i]=0;
//初始化树状数组
longlongans=0;
++i)
update(reflect[i]);
ans+=i-getsum(reflect[i]);
printf("
%lld\n"
ans);
}
POJ2352Stars
题目链接:
http:
//poj.org/problem?
id=2352
start数星星(POJ2352)
天文学家时常调查星图,星图在一个平面上表示出所有星星,而且每个星星都有笛卡尔坐标。
星星的级别是不在它上面且不在它右面的星星的总数。
天文学家想知道每个星星的
级别。
举例来说,看上面的图。
5号星星的级别是3(由1,2和4号而得)。
2点和4号星星的级别是1.在这个图里只有一个级别为0的星星,两个级别为1的星星,一个级别为2的星星和一个级别为3的星星。
你的任务是计算每个级别的星星总数
Input
第一行包含一个数N(1<
=N<
=15000)。
一下N行描述相应的星星(每行两个用空格隔开的整数X和Y,0<
=X,Y<
=32000)。
平面上每个坐标最多只有一个星星。
星星按Y的升序排列,Y相等则按X的升序排列
Output
输出包含N行,每行一个数。
第一行包含级别为0的星星的总数,第二行是级别为1的星星的总数,如此类推,最后一行是级别为N-1的星星的总数
SampleInput
5
11
51
71
33
55
SampleOutput
1
2
/*
就是求每个小星星左小角的星星的个数。
坐标按照Y升序,Y相同X升序的顺序给出
由于y轴已经排好序,可以按照x坐标建立一维树状数组
constintMAXN=15010;
constintMAXX=32010;
intc[MAXX];
//树状数组的c数组
intcnt[MAXN];
//统计结果
(-x);
voidadd(inti,intval)
=MAXX)
c[i]+=val;
i+=lowbit(i);
intsum(inti)
s+=c[i];
i-=lowbit(i);
intn;
intx,y;
while(scanf("
&
n)!
=EOF)
memset(cnt,0,sizeof(cnt));
for(inti=0;
scanf("
%d%d"
x,&
y);
//加入x+1,是为了避免0,X是可能为0的
inttemp=sum(x+1);
cnt[temp]++;
add(x+1,1);
printf("
cnt[i]);
第06讲二维树状数组
BIT可用为二维数据结果。
假设你有一个带有点的平面(有非负的坐标)。
你有三个问题:
1.在(x,y)设置点
2.从(x,y)移除点
3.在矩形(0,0),(x,y)计算点数-其中(0,0)为左下角,(x,y)为右上角,而边是平行于x轴和y轴。
对于1操作,在(x,y)处设置点,即Update(x,y,1),那么这个Update要怎么写?
很简单,因为x,y坐标是离散的,所以我们分别对x,y进行更新即可,函数如下:
voidUpdate(intx,inty,intval)
inty1=y;
while(y1<
C[x][y1]+=val;
y1+=y1&
(-y1);
x+=x&
那么根据Update可以推得:
GetSum函数为:
intGetSum(intx,inty)
intsum=0;
while(x>
while(y1>
sum+=C[x][y1];
y1-=y1&
x-=x&
第07讲二维树状数组的应用举例
id=2155
我们先讨论POJ2155的一维情况,如下:
有一个n卡片的阵列。
每个卡片倒放在桌面上。
你有两个问题:
1.Tij(反转从索引i到索引j的卡片,包括第i张和第j张卡——面朝下的卡将朝上;
面朝上的卡将朝下)
2.Qi(如果第i张卡面朝下回答0否则回答1)
解决:
解决问题(1和2)的方法有时间复杂度O(logn)。
在数组f(长度n+1)我们存储每个问题T(i,j)——我们设置f[i]++和f[j+1]--。
对在i和j之间(包括i和j)每个卡k求和f[1]+f[2]+...+f[k]将递增1,其他全部和前面的一样(看图2.0清楚一些),我们的结果将描述为和(和累积频率一样)模2。
图2.0
使用BIT来存储(增加/减少)频率并读取累积频率。
理解了一维的情况,POJ2155就是其二维的版本,易得只需要更(x1,y1),(x1,y2+1),(x2+1,y1),(x2+1,y2+1)四个点的C[]的值就可以了,最后的结果依然是G
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 树状 数组 C+