[置顶] 数据结构总结

鉴于我已经不会写树状数组[捂脸],新开一坑QAQAQ

树状数组

树状数组支持

  • 单点修改+区间和查询
  • 单点修改+区间最值查询
  • 区间加减+单点查询

查询/修改区间最值,查询/修改区间和,单点修改
lowbit(a)=aand(a)
定义 C[i]=A[ilowbit(i)+1]+...+A[i]
讲到树状数组必有的一张图

我们可以发现对于任意一个 C[i] ,如果修改了的话,会影响到的是 C[i+lowbit(i)] ,所以每次向上依次修改即可,(写成递归形式)
查询 [L,R] sum 时,变成 sum[1,R]sum[1,R]

一维单点修改+区间最值查询

单点修改时,修改c[i]需要比较的是 c[i20],c[i21]c[ilowbit(i)+1]
区间最值查询时,对于一个区间[L,R],如果R-lowbit(R)+1在[L,R]内那么取c[R-lowbit(R)+1,R]比较,如果不在,那么R变R-1
[BZOJ 1012] [JSOI2008] 最大数maxnumber

一维区间加减+单点查询

我们用差分序列来维护区间区间修改后的单点修改,因为差分序列序需要前缀和来单点查询,树状数组的区间查询正好就是靠前缀和来实现的,所以树状数组装的值就是差分的值
d[i]=a[i]a[i1]
单点查询 a[i]=ij=1d[j]
用树状数组维护 d[i] 这个序列即可
[BZOJ1782] [Usaco2010 Feb]slowdown 慢慢游

一维区间加减+区间查询

有上面单点查询的基础我们再来看区间查询
ni=1a[i]=ni=1ij=1d[j]=ni=1d[i](ni+1)=(n+1)ni=1d[i]ni=1d[i]i
(注意理解一下上面的推导)
所以我们维护 d[i]id[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]+val 为对原矩阵 (i,j)(n,m) 整体 +val
其中 n,m 为二维边界
例如修改矩阵为 (x1,y1)(x2,y2) ,那么我们执行
update(x1,y1,val)
update(x2+1,y1,val)
update(x1,y2+1,val)
update(x2+1,y2+1,val)
这四步即可
a[i,j]=ik=1jl=1d[k,l]
所以差分也可以理解为区间转化为点对它后面的贡献

二维矩阵加减+矩阵查询

    xi=1yj=1a[i,j]=xi=1yj=1ik=1jl=1d[k,l]=xi=1yj=1d[i,j](xi+1)(yj+1)
有一维的基础就很好理解了吧
然后我们把它展开QAQAQ
    xi=1yj=1d[i,j](xi+1)(yj+1)=(x+1)(y+1)xi=1yj=1d[i,j](x+1)xi=1yj=1d[i,j]j(y+1)xi=1yj=1d[i,j]i+xi=1yj=1d[i,j]ij
所以我们维护四棵树状数组 d[i,j],d[i,j]i,d[i,j]j,d[i,j]ij 即可
切记 树状数组下标要从1开始,因为lowbit(0)=0,就进入死循环了
每次各种操作的复杂度都是 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(N2) 级别,我们发现,这其中我们只改了一条链,即 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/

离线相关

离线的基本思路就是:对于所有答案先左端点排序,然后处理处左端点的所有答案,然后考虑左端点向右移一位会带来什么影响

  • [BZOJ3339] Rmq Problem&&[BZOJ3585] mex
  • [BZOJ3747] [POI2015]Kinoman
  • [BZOJ1878] [SDOI2009]HH的项链
  • [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的标记,单点查询时只要查询某点的子树标记和即可

你可能感兴趣的:([置顶] 数据结构总结)