关于树上信息的维护(持续更新因为后面没听懂(逃))

1.Dfs序的一些奇奇怪怪的操作和改进

所谓dfs序,就是一棵树被 dfs 时所经过的节点的顺序。一般的作用是维护子树信息,如果记录 dfn[i] 表示 i 号点的 dfs 序,sze[i]表示 i 号点的子树大小,那么 x 是 y 的祖先等价于:dfn[y] ∈ [dfn[x], dfn[x]+sze[x]−1]

那么我们就可以利用dfs序来进行一些树上的操作:

1.维护一棵树,支持:子树加,链加,单点求值。

        对于子树加,相当于是 dfs 序列上的区间加。

        对于链加,可以看作是一个点到根上的路径加。

        可以把这次的修改放到这个点上,然后单点查询的时候在子树内区间询问贡献。

2.维护一棵树,支持:子树加,链加,子树求和。

        对于子树加,相当于是 dfs 序列上的区间加。

        对于链加,可以看作是一个点到根上的路径加。

        一个修改 (x,W) 对 y 有贡献当且仅当 y 为 x 的祖先。且贡献为(dep[x]−dep[y]+1)∗W。

        分离变量,即为dep[x]∗W+(1−dep[y])∗W。

        所以维护两个区间和就行了。

3.维护一棵树,支持:单点加,链求和。

        对于链求和,可以看作是一个点到根上的路径求和。

        一个修改 (x,W) 对 y 有贡献当且仅当 x 为 y 的祖先。

        相当于支持区间加,单点求值。

4.维护一棵树,支持:子树加,链求和。

        对于链求和,可以看作是一个点到根上的路径求和。

        一个修改 (x,W) 对 y 有贡献当且仅当 x 为 y 的祖先,且贡献为(dep[y]−dep[x]+1)∗W。

        分离变量,即为(dep[y]+1)∗W−dep[x]∗W。

        维护两个区间和即可。

5.维护一棵树,支持:子树加,链加,链求和。

        子树加对于链求和,可以看作是一个点到根上的路径求和。

        关于链加,(x,W) 对 y 的贡献分开讨论:

        (1)若 x 为 y 的祖先,那么贡献为 depx ∗W。

        (2)若 y 为 x 的祖先,那么贡献为 depy ∗W

        分开维护贡献即可。

然后将这些东东结合起来,就可以出一些毒瘤题了(逃)~

关于dfs序的一些小改进

1.括号序列

考虑 dfs 序,在 dfs 完一个点的时候,我们同样把他加入序列,这样的得到的序列叫做括号序列。对于一棵有根树,

如果子节点有序,那么这棵树唯一对应着一个括号序列。

例如下图:

这棵树的括号序列就是1233442551,我们发现,如果用左右括号来代替进与出,那么整个序列就可以变成( ( ( ) ( ) ) ( ) ),由这个序列我们可以还原出这棵树及其dfs序,由此可见,括号序列与树是唯一对应的。

这里贴一道例题:BZOJ4337 树的同构,传送门:https://blog.csdn.net/g21glf/article/details/82952739

然后再口胡一道例题:

给定一棵树,支持给某个点换父亲,子树修改,查询某个点子树点权和。

题解:结合括号序列,可以发现,一个子树一定在他的根节点的括号内,那么对于换父亲,就是将一段括号从一个括号内移动到另一个括号内,用平衡树来维护区间的左括号和,支持区间挪动,区间加即可。

2.欧拉序

考虑dfs序,在dfs完一个点的某个子树的时候,我们把他加入序列,这样得到的序列叫做欧拉序,还是上图:

这棵树的欧拉序就是12332441551,然而看了是不是很头大?没关系,咱们随便在树上找两个点,看看他们和他们的lca在欧拉序中的位置关系,没错,我们发现两棵不同子树之间一定“夹”住了他们的lca,由这个性质我们可以利用st表进行O(nlog n)的预处理,然后就能实现O(1)复杂度求lca了!(然而还是树剖比较简单,下次更新就写树剖)

2.关于树链剖分的一些感受

树和序列其实有相同之处,我们可以把树分成若干个序列来维护。常用的技巧就是树链剖分。

我们把一棵树的边分为轻边和重边,保证每个节点与它的儿子之间只有一条重边,如下图:

其中红色加粗边就是这棵树的重边,具体的,size[u]记表示以 u 为根的子树的结点个数,令v为u所有儿子中size值最大的一个儿子,则(u,v)为重边,v称为u的重儿子。u到其余儿子的边为轻边。

这样每个点都可以沿着重边往下走到某个叶子节点,或者往上走到某条轻边或者根节点。我们可以把这一段信息一起维护。这样这棵树的信息就会被储存在若干条链上,这就是树链剖分的本质。

轻重边有如下几个性质:

1.如果(u,v)为轻边,那么size[v]<=size[u]/2(否则v就是重儿子)。

2.从根到某一点v的路径上最多有log n条轻边(每经过一条轻边,节点数至少缩为原先的一半,最多log次就为0)

3.我们称某条路径为重路径(链),当且仅当它全部由重边组成。那么对于每个点到根的路径上都不超过 O(log n) 条轻边和 O(log n) 条重路径。(因为每条重路径两端都是轻边,最多有log n条轻边,就最多有log n条重路径)

利用树链剖分求LCA

首先,x,y的LCA要么在两个点所在重链上,或者在该重链上方的重链上。

当 x, y不在一条重链上时,dep[top]较小的点所在的重链肯定不是要求的链,我们把这个点向上跳到fa[top]处。

当在一条链上时,深度较小的点为LCA。

因为一个点最多跳O(log n)次,所以时间复杂度为O(log n),而且常数比倍增小。

代码:

#include<bits/stdc++.h>
using namespace std;
const int MAXN=2e5+10;

int n,m,cnt;
int head[MAXN],depth[MAXN],fa[MAXN],son[MAXN],siz[MAXN];
int nxt[MAXN],to[MAXN];

void add(int x,int y)
{
    cnt++;
    nxt[cnt]=head[x];
    head[x]=cnt;
    to[cnt]=y;
}

void dfs1(int x,int fa)
{
    depth[x]=depth[fa]+1;
    siz[x]=1;
    for(int i=head[x];i!=-1;i=nxt[i])
    {
        int v=to[i];
        if(v!=fa)
        {
            fa[v]=x;
            dfs1(v,u);
            siz[x]+=siz[v];
            if(son[x]<siz[v])
              son[x]=v;
        }
    }
}

void dfs2(int x,int fa)
{
    if(x==son[fa])
      top[x]=top[fa];
    else
      top[x]=x;
    for(int i=head[x];i!=-1;i=nxt[i])
    {
        int v=to[i];
        if(v!=fa)
          dfs2(v,u);
    }
}

void init(int root)
{
    dfs1(root,-1);
    dfs2(root,-1);
}

int lca(int x,int y)
{
    while(top[x]!=top[y])
    {
        if(depth[top[x]]<depth[top[y]])
          swap(x,y);
        x=fa[top[x]];
    }
    return depth[x]<depth[y]?x:y;
}

int n,m,x,y;
int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;++i)
    {
        scanf("%d%d",&x,&y);
        add(x,y),add(y,x);
    }
    int q;
    scanf("%d",&q);
    while(q--)
    {
        scanf("%d%d",&x,&y);
        printf("%d\n",lca(x,y));
    }
    return 0;
}

 

例题:BZOJ1036,题目+题解传送门:https://blog.csdn.net/g21glf/article/details/82956343

然后就是利用树链剖分进行的一系列操作,待更新。。。


2018.10.7.12:00

posted @ 2018-10-07 16:25  Ishtar~  阅读(271)  评论(0编辑  收藏  举报