【算法】树

★ 无向无环连通图=树

树上路径问题除了考虑树链剖分,还可以考虑离线树上差分。

树上路径差分:x到根+y到根-lca(x,y)到根+fa[lca(x,y)]到根

【最近公共祖先(LCA)】

http://blog.csdn.net/wendavidoi/article/details/50670052

void dfs(int x,int fa)
{
    for(int i=1;(1<<i)<=deep[x];i++)
        f[x][i]=f[f[x][i-1]][i-1];
    for(int i=first[x];i;i=e[i].from)if(e[i].v!=fa)
    {
        int y=e[i].v;
        f[y][0]=x;
        deep[y]=deep[x]+1;
        dfs(y,x);
    }
}
int find(int x,int y)
{
    if(deep[x]<deep[y])swap(x,y);
    int d=deep[x]-deep[y]; 
    for(int i=0;(1<<i)<=deep[x];i++)
        if((1<<i)&d)x=f[x][i];
    if(x==y)return x;
    for(int i=20;i>=0;i--)
    if((1<<i)<=deep[x]&&f[x][i]!=f[y][i])
    {
        x=f[x][i];y=f[y][i];
    }
    return f[x][0];
}
倍增求LCA

无向边建两条,数组开大!

倍增数组的构造可以独立出来,只要第一重循环是倍增倍数就可以保证要用的已经算过。

事实证明,简单的路上路径问题用倍增比链剖更有优势。

性质:LCA其实就是两点到达根节点的路径的最近交点。

性质:树上n个点至多有n-1个LCA,就是每两个dfs序相邻点的LCA。

【树链剖分】

http://blog.csdn.net/y990041769/article/details/40348013

http://blog.csdn.net/acdreamers/article/details/10591443

为什么要把节点数多的子树作为重链?因为对于同一根,重链查询最快,而轻链必须跳一步才能到重链,这就最大程度减少跳跃次数。

假设最左端叶子节点要查询1次,则必须树大小为1*2+1=3;查询两次,树大小为3*2+1=7;也就是说对于节点数n的树剖分后查询的跳跃次数至多为log(n)次。

不过,如果要把链再放到线段树处理就是O(log2(n))了。

(模板)【BZOJ】1036 [ZJOI2008]树的统计Count

void dfs1(int x,int fa)
{
    size[x]=1;
    for(int i=first[x];i;i=e[i].from)
     if(e[i].v!=fa)
      {
          int y=e[i].v;
          deep[y]=deep[x]+1;
          fa[y]=x;
          dfs1(y,x);
          size[x]+=size[y];
      }
}
void dfs2(int x,int tp,int fa)
{
    int k=0;
    pos[x]=++dfsnum;
    top[x]=tp;
    for(int i=first[x];i;i=e[i].from)
     if(e[i].v!=fa&&size[e[i].v]>size[k])k=e[i].v;
    if(k==0)return;
    dfs2(k,tp,x);
    for(int i=first[x];i;i=e[i].from)
     if(e[i].v!=fa&&e[i].v!=k)dfs2(e[i].v,e[i].v,x);
}
int find(int x,int y)
{
    int sum=0;
    while(top[x]!=top[y])//while不是if
     {
         if(deep[top[x]]<deep[top[y]])swap(x,y)//比较顶点深度
        sum+=seg_sum(1,pos[top[x]],pos[x]);
        x=fa[top[x]];
     }
    if(pos[x]>pos[y])swap(x,y);
    lca=x;
    sum+=seg_sum(1,pos[x],pos[y]);
    return sum;
}
View Code

过程:

<dfs1>计算子树节点数,同时记录深度和父亲。

<dfs2>分配pos编号并计算top链

<solve>top不同时深度大的往上靠(注意是比较top的深度!注意用while不是if!),同top后pos

求LCA:询问时u,v往同一条重链靠近,到达同一条重链时,pos较小的就是LCA。

【树的直径】

树上简单最长路,树上最远点对。

证明:树的直径(最长路) 的详细证明

求树的直径:①从任意点BFS找到树的直径一端,再从该点BFS找到直径。②DP记录最深和次深,组合比较最长路。

拓展:树的直径相当于选择整棵树为点集的树上最远点对,故选定点集的树上最远点对也有以下性质。

性质:距离树上任意点的最远的点一定是直径的一端(因为直径的证明没有涉及选定点本身,所以任意点可以是点集外的点)。

 

【树上所有点的最远点】<1>第一次tree dp,得出f[i]表示i为根的子树的最深叶子路径,g[i]表示次深叶子路径。<2>第二次treedp,对于节点x,传下来s表示向上的最长路,所以s[x]=max(s,f[x])。遍历x儿子son[x]时s的传递:若f[son[x]]+1=f[x]则s=g[x]+2否则s=f[x]+2。

【树的重心】

引用自:关于树的重心的自我理解

重心的三个等价定义:

<1>最大的子树节点数最小

<2>子树节点数均<=sum/2(否则往子树移动,根往上这棵树更小)(当存在子树节点数为sum/2时,该子节点也为重心,即双重心)

<3>★所有点到重心的距离和最小

树形DP求重心:令d[i]为节点数,则d[i]=∑[j]+1,对于不同节点i,比较所有节点的max(d[j],n-d[i])的大小。

重心也可以基于点权定义,也就是d[i]=∑d[j]+w[i],定义sum=∑w[i],比较所有节点的max(d[j],sum-d[i])。

★例题:【BZOJ】3302: [Shoi2005]树的双中心 && 2103: Fire 消防站 && 2447: 消防站

【虚树】

当询问总数有限制时,我们可以对每个询问建虚树,每次复杂度为O(ki),总复杂度O(Σki)。

虚树中保留特殊点和之间的LCA,根据k个点的相互LCA集合是按dfs排序后所有相邻点的LCA,可以快速求出虚树中的点。

然后按dfs序入栈,若栈顶不是当前点的祖先则弹出栈顶,若是则连边,如此可以快速建出虚树。

注意:垃圾回收保证复杂度。特殊点数组开两倍以存多余的LCA。

例题:【BZOJ】2286: [Sdoi2011]消耗战

 

posted @ 2016-12-21 13:51  ONION_CYC  阅读(525)  评论(0编辑  收藏  举报