树状数组小结
Posted u2003
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了树状数组小结相关的知识,希望对你有一定的参考价值。
树状数组小结
前言
- 在近三周的时间内,我主要复习了线段树和树状数组。
但是线段树在遇到一道压位黑题崩掉后就写不动了。 - 突发奇想,我决定给这段时间的复习内容写个总结。
本蒟蒻实力有限,如果某些做法比较拉请谅解 - 本总结默认你已经会树状数组的基本内容。
【壹】单点修改,区间和查询
- 最经典的要用树状数组维护的题。( \\(P.S.\\) 用线段树也可以,但是常数较大,并且码量大,
不优雅) - 属于树状数组基本内容,在此不多讲解。
【贰】区间修改相同值,单点查询
- 需要用差分解决,差分后求前缀和得到的就是单点的值。
- 同时,差分代表的是与前一个的差值,所以我们只需要修改一头一尾(对于区间 \\([a,b]\\) ,$ add(a,1),add(b+1,-1) $ )
- \\(O(n)\\) 预处理,\\(O(m \\log n)\\) 修改+查询
- 线段树的延迟标记可以达到相同的效率,但如上文所言,
不优雅
【叁】区间修改相同值,区间查询
- 虽然线段树码量大,但是
专业的人做专业的事,大部分情况还是让线段树来做要好一些(树状数组很难写) - \\(But\\) 在一些特殊情况,树状数组好用得多。
例题 校门外的树
- 一道罕见的区间修改区间查询的树状数组题。
- 因为查询的是树的种数,差分是难以解决的。
- 根据题意,可以转化成这个问题:查询一个区间与目前存在区间有交集的数目。
- 而这样对于每个存在的区间分三种情况:左端点在区间内,右端点在区间内,和左右端点都在区间内。但对于第三种情况是比较难解决的。
- 所以我们考虑改下定义:求与询问区间没有交集的数目。
- 这样就非常好解决了。分成两种情况:左端点在区间右边和右端点在区间左侧,树状数组优化左右端点位置前缀和。
- 但由于是前缀和,左端点在区间右边又要转化为总的减去左端点在区间左侧和区间内的情况。
- 那答案就是 总-(总-(左端点在区间左侧+左端点在区间内)-(右端点在区间左侧)= 左端点在查询区间右端点左侧+右端点在查询区间左端点左侧 。
例题 [SDOI2009] HH的项链
- 这道题按照常规思路肯定是在线查询,但很难维护(至少我没想出来)。
- 但这道题有一个特性:没有修改操作。
- 那么这道题完全可以离线做(很多题离线可以骗到很多分,这道题更是可以得到全分)。
- 我们可以把询问区间按右端点升序排序,然后每次更新到右端点的每个位置左侧的花的种类数,然后进行区间查询。
- 但花的种类数还是很难维护,原因在于没法快速判定或在空间允许范围内区间查询某个位置和另一个位置上的话是否相同。
- 这就是离线的优越性所在。因为更新到当前询问的右端点,所以必定是右端点往左延伸的一个区间,那么只要保证每种花最靠右的存在,
其它的拔掉,就能保证不重不漏。
【肆】树状数组找逆序对
- 这也是树状数组的经典用途,也是 \\(CDQ\\) 分治的必备知识。当然由于范围问题,大部分时候都得离散化。
- 这也属于基础内容,不再讲解。
例题 火柴排队
- 由于要让总的平方差最小,肥肠容易想到让两堆火柴大小排名相同的作为一对。
易证,欢迎各位读者自己证明。 - 而由于每次只能交换左右两个,那就类似于冒泡排序。但模拟冒泡排序显然T掉。
- 而用冒泡排序找的本质便是找逆序对个数。
- 那这道题的思路是先离散化,再按照其中一排火柴的位置顺序进行排序(最抽象的地方),最后利用树状数组找逆序对。
小练习 三维偏序(陌上花开)
- 模板,自己去做吧。
问就是作者到现在都还没去做。
【伍】树状数组+二分查找空位
- 在某些情况下,我们希望能快速找到空位。相比 \\(O(n)\\) 查找,\\(O(\\log n^2)\\) 看起来要更优秀一点 (\\(P.S.\\) 如 \\(DFS\\) 之类的题尽量不要使用,\\(DFS\\) 由于时间复杂度的问题,数据范围比较小,树状数组+二分由于常数问题,反而要慢)。
- 具体做法:
inline int find(int p){//找目前排名p的存在的点
int l=1;
int r=n;
while(l<=r){
int mid=(l+r)/2;
if(ask(mid)>=p)r=mid-1;
else l=mid+1;
}
return l;
}
例题 [SHOI2013] 洗牌
这道题作为紫题还是有点太水了- 这道题给的数数量算中等,首先考虑模拟。
- 每次向后跳给定的切牌的数量(切的牌都是牌顶的牌),同时如果超过了n,就再从头开始跳。不过由于荷官可能会切好完整幅牌很多次,所以要进行取模。
减少工作量。
p=(p+r)%(n-i+1)==0?n-i+1:(p+r)%(n-i+1);
\\\\p表示每一轮取的是牌顶的第几张,r表示切了几张牌
小练习 Intervals
- 双倍经验
- 这道题做法很多,可以用差分约束,可以用贪心+线段树,也可以用贪心+树状数组+二分找空位。
- 由于这道题的重心并不是在数据结构维护上,所以不多讲解,留作读者思考。
- \\(P.S.\\) 这道题也提醒了我们,树状数组只是个数据结构,它是用来辅助的,而不一定是主要的。裸的树状数组花样肯定没有在某个地方偷偷塞一个的花样多,不能因为重点不在数据结构就不去思考用数据结构进行优化。
【陆】二维树状数组
- 二维树状数组,就是树状数组套树状数组,用于解决二维平面等情况下的单点查询区间修改,区间修改单点查询等问题。
- 一维树状数组转到二维树状数组和一维数组转到二维数组思路相同,用两层循环解决。
void add(int x,int y,int c){
for(int i=x;i<=n;i+=i&-i)
for(int j=y;j<=m;j+=j&-j)
sz[i][j]+=c;
}
int ask(int x,int y){
int ans=0;
for(int i=x;i<=n;i+=i&-i)
for(int j=y;j<=m;j+=j&-j)
sz[i][j]+=c;
return ans;
}
小练习 板子1 板子2
- 但如果二维数组控制范围太大,显然需要离散化。但是即使离散化了,空间也得开点数*点数。所以在有些情况下我们得压维。(当然,线段树也能做,叫做扫描线)
例题 [SHOI2007] 园丁的烦恼
- 这道题开二维数组是开不下的,只能压成一维。
- 由于没有修改操作(或者说修改操作与询问操作分离),这道题也可以(只能)强制离线,把一个询问拆成4个询问。
- 显然,压成一维时我们只能查找一个维度 \\(x\\) 上的前缀和,另一个维度 \\(y\\) 只能被表示出来,而我们需要查找的是一个点在两个维度上的前缀和。
- 显然,我们不希望只有一维时该点还算进了后方位置,那只要还没建后方的位置不就行了!便先按 \\(y\\) 维度升序排序,再按 \\(x\\) 维度升序排序,按顺序依次修改。(可以理解为就是扫描线····)
- 而这道题我们已经离线了,询问拆成4个后也要一起排序。当然,一定要注意如果询问和修改的两个坐标相同,优先修改。
例题 [SDOI2009] 虔诚的墓主人
-
这道题很有意思。因为它不仅对前缀有要求,还对后缀有要求。
-
但是如果我们预处理提前记住每一行每一列有多少个数,后缀自然轻松解决。
-
通过数据范围能显然看出得要离散化+压维(前提是知道用树状数组。
逃)。 -
但相信很多同学看到是个空地,心想那么大的地方,树就几棵,空地千千万,这时间不就炸掉了吗!而且不能离散化!
蚌埠住了。 -
实则忽略了重要限制条件,上下左右都得有 \\(k\\) 棵常青树,也就是说,只有空地所在的行和列都得有常青树才行。
-
同时由于上下左右可能不止 \\(k\\) 棵,随便选就行,显然组合数 \\(\\mathrm{C}_n^k\\)。
-
那我们就在扫描的同时计算答案。设目前的种树的坐标是\\((x,y)\\) ,上一次种树的坐标是 \\((x,y\')\\) ,则在\\(y\'+1 \\to y-1\\) 范围内,左右各选 \\(k\\) 棵的方案数一样。那我们只需要算出来区间每个位置上下各选 \\(k\\) 棵树的方案数,用树状数组维护以便查找区间和。
-
所以这道题用树状数组维护的内容不再是有上方几棵树,而是每个位置的方案树。每次种树时进行单点修改方案树。
【柒】总结
-
树状数组不属于不教就不会的类型,大部分变形完全可以在考场上现推出来的,这就需要诸位读者勤思考。
-
树状数组大部分都可以用线段树代替,并且有不少功能只有用线段树才能实现。这并不意味着树状数组死了,它依然以其优美的实现方式,简洁的代码,常数小等优点活跃。
-
说到底,树状数组也只是一个数据结构。树状数组,线段树,平衡树,会敲板子是没用的。不和其它问题糅到一起,用来优化算法,它是不完整的,残缺的。会写重要,但会做更重要。
\\(\\cal {Made \\ By \\ YuGe}\\)
以上是关于树状数组小结的主要内容,如果未能解决你的问题,请参考以下文章