7.6集训Day1 树状数组进阶
今天的集训怎么说,算是来了个下马威吧~~(虽然我暂时还没去听课)~~。
但是看着zyz大佬找的五道题和一道自己出的题,最低也是个蓝色的,最高的没有评级,但我觉得这道题可以评黑(ICPC 2018 WF的Triangles)。多少有点害怕了属于是。
废话少说,来开始今天的学习总结。
【复习】基础的树状数组知识!
性质:任意正整数关于2的不重复次幂的唯一分解性质,[1,x][1,x][1,x]区间可以被分成O(logx)O(logx)O(logx)个小区间。
这些子区间的共同特点是:若区间结尾为RRR,则区间长度就等于RRR的“二进制分解”下的最小的222次幂,设为lowbit(R)lowbit(R)lowbit(R)。
对于给定的序列AAA,建立一个数组ccc,其中c[x]c[x]c[x]保存AAA的区间[x−lowbit(x)+1,x][x-lowbit(x)+1,x][x−lowbit(x)+1,x]中所有数的和。
该结构满足以下性质:
(1)每个内部结点c[x]c[x]c[x]保存以它为根的子树中所有叶结点的和。
(2)每个内部结点c[x]c[x]c[x]的子节点个数等于lowbit(x)lowbit(x)lowbit(x)的大小。
(3)除了树根之外,每个内部结点c[x]c[x]c[x]的父节点是c[x+lowbit(x)]c[x+lowbit(x)]c[x+lowbit(x)]
(4)树的深度是O(logN)O(logN)O(logN)
树状数组一个很有意思的点是初始化。假设源数组与树状数组初始均为零,则树状数组就是通过依次调用add(i,A[i])add(i,A[i])add(i,A[i])来实现的。
同时,其实不需要专门保存源数组,因为所有源数组的操作与查询全部都映射到了树状数组上,即所有需要用到的信息树状数组都有保存,无需额外再保存源数组。在很多数据结构中都是这样的情况,源数据结构其实不用保存。
一维树状数组:
单点修改单点查询
#define lowbit(x) x&-x
void add(int x,int y){
for(;x <= N;x += lowbit(x)) c[x] += y;
}
int ask(int x){
int ans = 0;
for(;x;x -= lowbit(x)) ans += c[x];
return ans;
}
//查询前缀和:sum(y)-sum(x-1)
区间修改单点查询
考虑源数组AAA,令前缀和数组为S,有Si=ΣAiS_i=\Sigma{A_i}Si=ΣAi。反过来称AAA是SSS的差分数组。即,如果令AAA为源数组,则其差分数组DDD为:
? ?D1=A1?Di=Ai−Ai−1?\begin{array}{c} ? D_{1}=A_{1} \\ ? D_{i}=A_{i}-A_{i-1} ? \end{array}?D1=A1?Di=Ai−Ai−1?
如果针对差分数组D建立一个树状数组,就可以在logloglog时间内完成DDD上的单点操作,也就相当于可以在logloglog时间内完成源数组的区间修改操作。不过因为此时树状数组求的是差分数组的前缀和,实际上就是AAA中某个元素的值,所以,此时树状数组支持的是区间修改、单点查询。
int c[N+5]; //c[i] = a[i] - a[i-1]
void add(int x,int y){
for(;x <= N;x += lowbit(x)) c[x] += y;
}
void add_range(int l,int r,int v){
add(l,v);add(r+1,-v);
}
int ask(int x){
int sum = 0;
for(;x > 0; x -= lowbit(x)) sum += c[x];
return sum;
}
区间修改区间查询
开一个bbb数组,起初全为0
我们用树状数组维护bbb数组,对于每条ADD l r dADD~~l~~r~~dADD l r d的指令,把b[l]+=db[l]+=db[l]+=d,再把b[r+1]−=db[r+1]-=db[r+1]−=d。
我们已经知道,bbb数组的前缀和Σi=1xb[i]\Sigma^{x}_{i=1}b[i]Σi=1xb[i]就是经过这些指令后a[x]a[x]a[x]增加的值。
则aaa的前缀和a[1∼x]a[1\sim x]a[1∼x]增加的值就是Σi=1xΣj=1ib[j]\Sigma^{x}_{i=1} \Sigma^{i}_{j=1} b[j]Σi=1xΣj=1ib[j]
展开得:Σi=1xΣj=1ib[j]=Σi=1x(x−i+1)∗b[i]=(x+1)Σi=1xb[i]−Σi=1xi∗b[i]\Sigma^{x}_{i=1} \Sigma^{i}_{j=1} b[j]=\Sigma^{x}_{i=1}(x-i+1)*b[i]=(x+1)\Sigma^{x}_{i=1}b[i]-\Sigma^{x}_{i=1}i*b[i]Σi=1xΣj=1ib[j]=Σi=1x(x−i+1)∗b[i]=(x+1)Σi=1xb[i]−Σi=1xi∗b[i]
增加一个树状数组,用于维护i∗b[i]i*b[i]i∗b[i]上的前缀和Σi=1xi∗b[i]\Sigma^{x}_{i=1}i*b[i]Σi=1xi∗b[i]。则可以直接计算上式
则有以下四个操作
(1)C0C_0C0:在lll上的数加ddd (2)C0C_0C0:在r+1r+1r+1上的数减ddd
(3)C1C_1C1:在lll上的数加l∗dl*dl∗d(4)C1C_1C1:在r+1r+1r+1上的数减(r+1)∗d(r+1)*d(r+1)∗d
另外建立数组sumsumsum存储aaa的原始前缀和,对于每条指令Ask l rAsk~~l~~rAsk l r,拆成1∼r1\sim r1∼r和1∼l−11 \sim l-11∼l−1两个部分,二者相减
(sum[r]+(r+1)∗ask(c0,r)−ask(c1,r))−(sum[l−1]+l∗ask(c0,l−1)−ask(c1,l−1))(sum[r]+(r+1)*ask(c_0,r)-ask(c_1,r))-(sum[l-1]+l*ask(c_0,l-1)-ask(c_1,l-1))(sum[r]+(r+1)∗ask(c0,r)−ask(c1,r))−(sum[l−1