鉴于我已经不会写树状数组[捂脸],新开一坑QAQAQ
##树状数组
树状数组支持
- 单点修改+区间和查询
- 单点修改+区间最值查询
- 区间加减+单点查询
查询/修改区间最值,查询/修改区间和,单点修改
l
o
w
b
i
t
(
a
)
=
a
a
n
d
(
−
a
)
lowbit(a)=a and (-a)
lowbit(a)=aand(−a)
定义
C
[
i
]
=
A
[
i
−
l
o
w
b
i
t
(
i
)
+
1
]
+
.
.
.
+
A
[
i
]
C[i]=A[i-lowbit(i)+1]+...+A[i]
C[i]=A[i−lowbit(i)+1]+...+A[i]
讲到树状数组必有的一张图
我们可以发现对于任意一个
C
[
i
]
C[i]
C[i],如果修改了的话,会影响到的是
C
[
i
+
l
o
w
b
i
t
(
i
)
]
C[i+lowbit(i)]
C[i+lowbit(i)],所以每次向上依次修改即可,(写成递归形式)
查询
[
L
,
R
]
[L,R]
[L,R]的
s
u
m
sum
sum时,变成
s
u
m
[
1
,
R
]
−
s
u
m
[
1
,
R
]
sum[1,R]-sum[1,R]
sum[1,R]−sum[1,R]
###一维单点修改+区间最值查询
单点修改时,修改c[i]需要比较的是
c
[
i
−
2
0
]
,
c
[
i
−
2
1
]
…
…
c
[
i
−
l
o
w
b
i
t
(
i
)
+
1
]
c[i-2^0],c[i-2^1]……c[i-lowbit(i)+1]
c[i−20],c[i−21]……c[i−lowbit(i)+1]
区间最值查询时,对于一个区间[L,R],如果R-lowbit®+1在[L,R]内那么取c[R-lowbit®+1,R]比较,如果不在,那么R变R-1
[BZOJ 1012] [JSOI2008] 最大数maxnumber
###一维区间加减+单点查询
我们用差分序列来维护区间区间修改后的单点修改,因为差分序列序需要前缀和来单点查询,树状数组的区间查询正好就是靠前缀和来实现的,所以树状数组装的值就是差分的值
令
d
[
i
]
=
a
[
i
]
−
a
[
i
−
1
]
d[i]=a[i]-a[i-1]
d[i]=a[i]−a[i−1]
单点查询
a
[
i
]
=
∑
j
=
1
i
d
[
j
]
a[i]=\sum_{j=1}^id[j]
a[i]=∑j=1id[j]
用树状数组维护
d
[
i
]
d[i]
d[i]这个序列即可
[BZOJ1782] [Usaco2010 Feb]slowdown 慢慢游
###一维区间加减+区间查询
有上面单点查询的基础我们再来看区间查询
∑
i
=
1
n
a
[
i
]
=
∑
i
=
1
n
∑
j
=
1
i
d
[
j
]
=
∑
i
=
1
n
d
[
i
]
∗
(
n
−
i
+
1
)
=
(
n
+
1
)
∗
∑
i
=
1
n
d
[
i
]
−
∑
i
=
1
n
d
[
i
]
∗
i
\sum_{i=1}^na[i]=\sum_{i=1}^n\sum_{j=1}^id[j]=\sum_{i=1}^nd[i]*(n-i+1)\\=(n+1)*\sum_{i=1}^nd[i]-\sum_{i=1}^nd[i]*i
∑i=1na[i]=∑i=1n∑j=1id[j]=∑i=1nd[i]∗(n−i+1)=(n+1)∗∑i=1nd[i]−∑i=1nd[i]∗i
(注意理解一下上面的推导)
所以我们维护
d
[
i
]
和
i
∗
d
[
i
]
d[i]和i*d[i]
d[i]和i∗d[i]两棵树状数组即可
###二维树状数组
一维的我们这么写
void update(int x,int val)
{
for(int i=x;i<=n;i+=lowbit(i))
bit[i]+=val;
}
二维的就
void update(int x,int y,int c,int val)
{
for(int i=x;i<=n;i+=lowbit(i))
for(int j=y;j<=m;j+=lowbit(j))
bit[i][j]+=val;
}
很简单吧
###二维矩阵加减+单点查询
我们定义对
d
[
i
,
j
]
+
v
a
l
d[i,j]+val
d[i,j]+val为对原矩阵
(
i
,
j
)
−
(
n
,
m
)
(i,j)-(n,m)
(i,j)−(n,m)整体
+
v
a
l
+val
+val
其中
n
,
m
n,m
n,m为二维边界
例如修改矩阵为
(
x
1
,
y
1
)
−
(
x
2
,
y
2
)
(x_1,y_1)-(x_2,y_2)
(x1,y1)−(x2,y2),那么我们执行
u
p
d
a
t
e
(
x
1
,
y
1
,
v
a
l
)
update(x_1,y_1,val)
update(x1,y1,val)
u
p
d
a
t
e
(
x
2
+
1
,
y
1
,
−
v
a
l
)
update(x_2+1,y_1,-val)
update(x2+1,y1,−val)
u
p
d
a
t
e
(
x
1
,
y
2
+
1
,
−
v
a
l
)
update(x_1,y_2+1,-val)
update(x1,y2+1,−val)
u
p
d
a
t
e
(
x
2
+
1
,
y
2
+
1
,
v
a
l
)
update(x_2+1,y_2+1,val)
update(x2+1,y2+1,val)
这四步即可
a
[
i
,
j
]
=
∑
k
=
1
i
∑
l
=
1
j
d
[
k
,
l
]
a[i,j]=\sum_{k=1}^i\sum_{l=1}^jd[k,l]
a[i,j]=∑k=1i∑l=1jd[k,l]
所以差分也可以理解为区间转化为点对它后面的贡献
###二维矩阵加减+矩阵查询
∑
i
=
1
x
∑
j
=
1
y
a
[
i
,
j
]
=
∑
i
=
1
x
∑
j
=
1
y
∑
k
=
1
i
∑
l
=
1
j
d
[
k
,
l
]
=
∑
i
=
1
x
∑
j
=
1
y
d
[
i
,
j
]
∗
(
x
−
i
+
1
)
∗
(
y
−
j
+
1
)
~~~~\sum_{i=1}^x\sum_{j=1}^ya[i,j]\\=\sum_{i=1}^x\sum_{j=1}^y\sum_{k=1}^i\sum_{l=1}^jd[k,l]\\=\sum_{i=1}^x\sum_{j=1}^yd[i,j]*(x-i+1)*(y-j+1)
∑i=1x∑j=1ya[i,j]=∑i=1x∑j=1y∑k=1i∑l=1jd[k,l]=∑i=1x∑j=1yd[i,j]∗(x−i+1)∗(y−j+1)
有一维的基础就很好理解了吧
然后我们把它展开QAQAQ
∑
i
=
1
x
∑
j
=
1
y
d
[
i
,
j
]
∗
(
x
−
i
+
1
)
∗
(
y
−
j
+
1
)
=
(
x
+
1
)
∗
(
y
+
1
)
∗
∑
i
=
1
x
∑
j
=
1
y
d
[
i
,
j
]
−
(
x
+
1
)
∗
∑
i
=
1
x
∑
j
=
1
y
d
[
i
,
j
]
∗
j
−
(
y
+
1
)
∗
∑
i
=
1
x
∑
j
=
1
y
d
[
i
,
j
]
∗
i
+
∑
i
=
1
x
∑
j
=
1
y
d
[
i
,
j
]
∗
i
∗
j
~~~~\sum_{i=1}^x\sum_{j=1}^yd[i,j]*(x-i+1)*(y-j+1)\\=(x+1)*(y+1)*\sum_{i=1}^x\sum_{j=1}^yd[i,j]-(x+1)*\sum_{i=1}^x\sum_{j=1}^yd[i,j]*j-(y+1)*\sum_{i=1}^x\sum_{j=1}^yd[i,j]*i+\sum_{i=1}^x\sum_{j=1}^yd[i,j]*i*j
∑i=1x∑j=1yd[i,j]∗(x−i+1)∗(y−j+1)=(x+1)∗(y+1)∗∑i=1x∑j=1yd[i,j]−(x+1)∗∑i=1x∑j=1yd[i,j]∗j−(y+1)∗∑i=1x∑j=1yd[i,j]∗i+∑i=1x∑j=1yd[i,j]∗i∗j
所以我们维护四棵树状数组
d
[
i
,
j
]
,
d
[
i
,
j
]
∗
i
,
d
[
i
,
j
]
∗
j
,
d
[
i
,
j
]
∗
i
∗
j
d[i,j],d[i,j]*i,d[i,j]*j,d[i,j]*i*j
d[i,j],d[i,j]∗i,d[i,j]∗j,d[i,j]∗i∗j即可
切记 树状数组下标要从1开始,因为lowbit(0)=0,就进入死循环了
每次各种操作的复杂度都是
O
(
l
o
g
n
)
O(logn)
O(logn)
##线段树
这个其实很简单,所以基本的YY一下就好了
- 线段树的核心是标记
- 强烈推荐线段树套题SPOJ GSS系列!!(1~5是裸的线段树,6要支持插入删除是平衡树,7是树剖)
- 有些题目会因为修改的次数有限所以暴力修改可以维持均摊O(logN)
- Codeforces Round #250 D - The Child and Sequence/[TYVJ3838] DQS和序列(by 帝江&Darkfalmes) 取模操作
- [BZOJ3211] 花神游历各国/[BZOJ3038] 上帝造题的七分钟2 开根号操作
- 标记设计要包括所有情况
- 要仔细讨论标记的下放顺序
- [BZOJ1798] [Ahoi2009]Seq 维护序列seq
- [BZOJ1593] [Usaco2008 Feb]Hotel 旅馆
- [BZOJ1858] [Scoi2010]序列操作
- 内存方面,线段树基本要开到4倍空间以上,如果你开了4倍你要注意,在下方标记pushdown时,要先判断下标是否超,再修改,先修改再判断会RE
procedure pushdown(a:longint);
begin
if x[a,1]=x[a,2] then begin x[a,4]:=0; exit; end;
inc(x[a*2,3],x[a,4]); inc(x[a*2,4],x[a,4]);
inc(x[a*2+1,3],x[a,4]); inc(x[a*2+1,4],x[a,4]);
x[a,4]:=0;
end;
##主席树/可持久化线段树
主席树可以用来维护静态/动态询问区间第k大
%%%CLJ《可持久化数据结构研究》
###关于第k大
我们对于所有数字离散化后建立权值线段树,维护离散化后大小点值在[L,R]内的数的个数,查询时,如果[L,mid]中的数的个数小于k,那么查询[mid+1,R],反之亦然
###静态区间第k大
不支持修改,依次加入第i个数,加入一个数是以新建立一棵线段树的方式进行的,就是对于第i-1个数建立的线段树中包含第i个数的区间内+1,但如果全部新开节点的话会达到
O
(
N
2
)
O(N^2)
O(N2)级别,我们发现,这其中我们只改了一条链,即
l
o
g
N
logN
logN个节点的值,所以只要对这
l
o
g
N
logN
logN个节点新开即可,其他的节点都仍指向第i-1的数的节点
当询问[L,R] (注意这里的L,R不是权值)内的第k大时,我们从第L-1个数的根和第R个数的根同时向下走,那么区间内的数的个数就是R的权值-(L-1)的权值,根据前面提到的判断k大的方法即可
###动态区间第k大
用树状数组维护前缀和即可,但空间上存在常数优化的问题,在此不做展开
##可持久化Trie树
与主席树类似,都是依靠前缀和相减来取出区间,与Trie一样一般是异或问题
##平衡树
###Splay
移步我的另一篇文章Splay总结
##动态树
https://oi.abcdabcd987.com/summary-of-link-cut-tree/
##离线相关
离线的基本思路就是:对于所有答案先左端点排序,然后处理处左端点的所有答案,然后考虑左端点向右移一位会带来什么影响
-
[BZOJ2743] [HEOI2012]采花
##树上的数据结构问题
参考 许昊然《数据结构漫谈》
DFS序和树链剖分都是将树上的问题转化成序列上的问题的有效的方法
DFS序的性质:可以将某个点的子树转化为连续的一段
树链剖分 -
单点修改+单点查询
直接线段树/树状数组搞就行 -
单点修改+子树查询
dfs序让子树查询转为区间查询,线段树/树状数组 -
单点修改+树链查询
树链剖分 -
子树加减+单点查询
dfs序让子树修改转为区间修改,线段树/树状数组 -
子树加减+子树查询
dfs序,区间修改区间查询 -
子树加减,路径查询
树链剖分 -
路径修改,单点查询
树链剖分 -
路径修改,子树查询
树链剖分 -
路径修改,单点查询
-
这个就是树链剖分的裸题
-
但是我不会树链剖分QAQAQ(upd:会啦~~~)
-
把差分序列用在树上,要求点到根的和的时候,如果树的高度能维持在O(logN)的话就可以从根一路走下来了,但是显然会被卡,所以我们再维护一棵线段树,记录的权值为点到根的路径和,修改一点时他的子树所有点会同时改变相同值,这样就可以对于修改(a,b)的路径我们修改lca(a,b),son[a],son[b]的子树
upd:son[a],son[b]的数量…那么也会被卡… -
upd:以上都是口胡QAQAQ树上差分什么的才不可能实现的呢
-
以下是正解,
我们对于(a,b)的路径+w,相当于对于a到根+w,b到根+w,lca(a,b)到根-w,fa[lca(a,b)]到根-w
所以对于a,b同时打上+w的标记,对于lca(a,b)和fa[lca(a,b)]同时打上-w的标记,单点查询时只要查询某点的子树标记和即可