zoukankan      html  css  js  c++  java
  • 支配树学习笔记

    我快菜死了!!!

    我看了一天半的理论,又找了一下午的板子!QAQ终于结束了!!!

    QAQ首先感谢优秀的大佬的博客以及人民教师lyd对于我疑惑的解答

    QAQ顺便下次在提问前要先看题意啊!

     

    必经点:
    给定一张有向图,起点为S,终点为T。若从S到T的每条路径都经过一个点x,则称点x是有向图中从S到T的必经点

    支配树定义:
    对于一张有向图(可以有环)我们规定一个起点s,从s点到图上另一个点t可能存在多条路径
    对于从s到t的任意路径中,都存在一个点p,即从s到t必须经过p,那么我们称p为t的支配点(注意r点不讨论支配点)
    用idom[u]表示离点t最近的支配点(很显然,支配点不止一个,且支配点等价于必经点)
    对于原图除s外,每一个点t,从idom[t]向t建一条边,最后我们得到了一颗以s为根的树,这棵树就是“支配树(Dominator tree)

    割点和支配点的区别
    考虑从起点s到终点t,删掉那些点会使s无法到达t
    很显然,删掉任意一个从起点s到终点t的必经点就能使s无法到达t,删掉任意一个非必经点,s仍可到达t
    从支配树的角度考虑,只需删掉树上s到t路径上任意一点即可
    从割点去考虑,割点可以使一张变成两个子图,那么我们是否只需要考虑一下所有割点中在s到t的路径上的即可?是否将某个割点删掉,从s就无法到达t?
    稍微细想便知是不正确的:
    1.对于一张有向无环图:
    从s到t有多条路径,其中在一条路径上可能存在一个节点,我们设为u,删掉u后,图的连通块数量增加,但是可能这个点并不能阻挡s到达t,也就是说:u不是支配点
    即:割点不一定是支配点;

    同样支配点也不一定是割点
    2.对于一个环,不存在割点,但是环上,每一个点都是支配点。

    所以显然支配点和割点是不同的

    几个内容:
    1.从root出发可以到达G中所有的点,并且树上的每条边(u, v)都满足u == idom(v),即父节点是子节点的最近必经点
    2.也就是说,x == idom(y)当且仅当有向边(x, y)是支配树的一条树枝边
    3.x∈dom(y),当且仅当支配树中存在一条从x到y的路径
    4.x的必经点集合dom(x)就是支配树上x的所有祖先以及x自身

    对于一棵树:
    我们用root表示根节点,son表示树上的某个非根节点,很容易发现,从root到son的路径上所有的节点都是son的支配点,且idom[son]是son的父亲节点

    对于DAG(有向无环图)
    DAG上的问题当然是直接上拓扑排序,也就是说,我们可以按照拓扑排序来构建支配树
    假设我们当前构造到拓扑序中第x节点,节点编号为k,那么就是说,在拓扑序中1—x-1个节点已经处理好了
    那么对于考虑所有能直接到达点k的节点,然后我们求出这些节点在支配树上的最近公共祖先Y,那么Y就是点k在支配树上的父亲
    若使用倍增求LCA,最终复杂度为O((n+m)logn)(预处理是nlogn,查询一次是logn,多次查询最多为O((n+m)logn))

    接下来考虑过一个普通的有向图
    最基本的方法是枚举尝试删除每一个点,然后BFS,复杂度为O(nm)

    更快的方法:Lengauer-Tarjan

    一些概念:
    半支配(必经)点:

    先搞一颗搜索树,用dfn[x]表示x的dfs序在哪里,也就是x的时间戳是什么
    对于dfs树,有一个重要性质,对于两个节点u, v,若u, v是图中节点且dfn[u] <= dfn[v],则任意从u到v的路径必然包含他们在dfs树的一个公共祖先(画一个图后就清楚了,不在详细证明,当然我也不会)


    我们用idom表示点x最近的支配点,用semi[x]表示点x的半支配点
    半支配点的定义:对于一个节点Y,存在某个点X能够通过一系列点pi(不包含X和Y)到达点Y且对于任意i,都满足dfn[pi] > dfn[Y](节点pi的时间戳大于节点Y)我们称X是Y的半支配点,记做semi[Y] = X
    一个点k的半支配点会有很多个,而且根据dfs树的性质,这些半支配点一定是搜索树中点k的祖先


    对于每个点,只保存他的半支配点中时间戳最小的一个,使用semi[x]表示点x的半支配点中时间戳(dfn值)最小的点的编号
    则:
    对于一个节点Y,在所有能够到达它的节点中,设其中一个为X,
    1.若dfn[X] < dfn[Y],则X是Y的半支配点
    2.若dfn[X] > dfn[Y],那么对于X在搜索树中的祖先Z(包括X本身),如果满足dfn[Z] > dfn[Y],那么semi[Z]也是Y的半支配点

    对于半支配点,我们可以知道以下性质:
    ·1.每个节点的semi是唯一的。
    ·2.对于一个节点x,semi[x]必定是它在搜索树上的祖先
    ·3.对于一个节点的semi,不一定是该节点的idom
    ·4.一个节点的semi的深度大于等于该节点的idom(若该节点的semi深度小于idom,根据semi的定义,从semi有多条路径不包含idom到达x,则idom不成立,因此对于某的节点的semi的深度大于或等于idom)
    ·5.对于两个节点u和v,如果u是v的祖先,那么u是idom[v]的祖先或者idom[v]是idom[u]的祖先。
    (对于第5点如果u是idom[v]的后代,并且idom(u)是idom(v)的祖先,那么就可以从根r绕过idom[v]走到u,再直接走到v,与已知矛盾。

    半支配点的意义:
    求出搜索树后,对于原图中的所有非树边,将这些边删掉,加入新的边{(semi[k], k) | k∈V && k != root},我们可以发现我们将原图改建成了DAG,并且对于这个新图每个节点的支配点是不变的
    接着我们可以使用DAG的做法来处理支配树吗,复杂度为O((n+m)logn)

    当然我们肯定会怀疑,搞了一大堆东西就是为了搞个这?
    所以显然我们还有更高效的做法

    支配点(必经点)
    (我们从最开始就在扯这个东西了(笑))
    显然一个点的半支配点可能是一个点的支配点,也可能不是

    定理:idom与semi的关系:(支配点定理)
    定义集合{P}表示搜索树种路径(semi[x], x)上的点集(不包括semi[x],但是包括x(hhhhhhh)),找到{P}中semi的时间戳最小的节点,设为z
    1.若semi[z] == semi[x],则semi[x] == idom[x]
    口胡瞎证明:
    对于从x到y的链,我们可以很容易想到,若semi[x]是x的父亲,semi[x]肯定就是支配点了
    那么我们假定semi[x]到x之间存在数个节点。
    首先我们选取的z的semi对于该路径上所有节点的semi来说,是时间戳最小的。
    也就是说,要想到达该路径上其他节点的semi,必须经过semi[x]。
    我最初的时候犯了一会迷,假定一个中间节点y,semi[y]<semi[z],那么对于semi[semi[y]],则节点semi[semi[y]]能够通过一个访问时间晚的节点访问到semi[y]
    从搜索树的树边和非树边角度考虑,我们默认搜索树是一颗二叉树,我们设semi[semi[y]]是根节点,
    也就是说上述非树边是从右子树横叉到左子树的semi[y]上的,
    同理,semi[y]通过右子树上一个节点横叉,连接到了y上,那么我们首先可以保证,对于右子树上的点,它们的时间戳肯定大于y,此时我们会发现,因为我们的半支配点保留的是时间戳最小的节点
    所以此时semi[y]应该为根节点,又因为根节点的时间戳是最小的,那么就是说对于这条路径上所有节点的semi值,semi[z]不再是时间戳最小的,则与我们的条件矛盾
    可知,对于从semi[x]到x的路径上,有节点z的semi值的时间戳最小,若要到达其余节点的semi,必须经过semi[z]
    这个时候我们可以发现:semi[z]实际上就能封闭整颗以它为根的子树,就是说,不存在任何横叉边能访问到除了x以外的节点
    此时我们考虑x,显然若semi[x] == semi[z],则整颗子树就被semi[x]封闭了,并且保证了时间戳最大,那么idom[x]自然就等于semi[x]

    2.若semi[z] != semi[x],则idom[z] == idom[x]
    口胡瞎证明*2:
    上述我们已知了semi[z]已经将整颗子树完成了封闭,由半支配点性质3可知,idom[z]的时间戳小于等于semi[z],
    我们现在已知semi[z]已经可以支配x的自由了,但是不能保证semi[z]被拴住。
    首先我们可以知道的是,支配显然是具有传递性的,idom[z]限制的是z的自由,那么同样,自然要防止semi[z]被到达,因此idom[z]能限制semi[z]的自由,所以求取idom[z]时,
    就保证了以idom[z]为根的整颗子树都在idom[z]的支配下,此时要想彻底防止到达x节点,idom[x]自然等于idom[z]

    以上均属口胡,如有出错,打脸轻点

    那么这样我们就能够通过semi求得idom了

    接下来要解决的就是如何求解得到semi

    哎等下.....上面好像讲过怎么求semi了.....翻车....
    我再给搬下来一次吧

    对于一个节点Y,在所有能够到达它的节点中,设其中一个为X,
    1.若dfn[X] < dfn[Y],则X是Y的半支配点
    2.若dfn[X] > dfn[Y],那么对于X在搜索树中的祖先Z(包括X本身),如果满足dfn[Z] > dfn[Y],那么semi[Z]也是Y的半支配点

    根据定义+画图,很好证明
    对于第一条:
    这是非常显然的啊
    对于第二条:
    之所以dfn[X],dfn[Z]会大于dfn[Y],之所以semi[Z]是Y的半支配点
    这都是定义:
    “对于一个节点Y,存在某个点X能够通过一系列点pi(不包含X和Y)到达点Y且对于任意i,都满足dfn[pi] > dfn[Y](节点pi的时间戳大于节点Y)我们称X是Y的半支配点,
    记做semi[Y] = X一个点k的半支配点会有很多个,而且根据dfs树的性质,这些半支配点一定是搜索树中点k的祖先对于每个点,只保存他的半支配点中时间戳最小的
    一个,使用semi[x]表示点x的半支配点中时间戳(dfn值)最小的点的编号”
    的选择啊
    意会一下就清楚了。
    这样怎么求semi和怎么求idom就全部清楚了

    最后彻底再介绍一下Lengauer-Tarjan算法的实现:
    Lengauer-Tarjan具体实现:
    对于求半支配点与支配点我们都必须处理一个问题,就是对于一个节点X的前驱Y,我们需要计算Y在搜索树树上所有dfn值大于dfn[X]的祖先中,semi值最小的一个。
    可以按照dfn值从大到小处理,利用并查集维护,这样处理到节点X时,dfn值比X大的都被维护起来了
    对于Y的所有满足条件的祖先,就是在并查集中Y的祖先,通过带权并查集的方法,维护祖先中的最小值,并记下semi最小的具体是哪个节点

    ·在访问每个节点时,把该节点放入图的一个生成森林中
    ·根据半支配点定理,对于一条边(x, y),若dfn[x] > dfn[y],则计算semi[y]时需要考虑x的祖先中比y的时间戳大的节点
    ·由于算法按照时间戳从大到小顺序计算,此时比y的时间戳小的节点还未加入生成森林,因此直接在生成森林中考虑x的所有祖先即可
    ·使用并查集维护生成森林,把dfn[semi[z]]作为z到其父节点的边的权值,在路径压缩过程中对每个节点维护一个变量,很容易计算出z到其祖先的所有边权的最小值
    这样,我们就能够在O((n + m) * α(n))时间内解决问题

     虽然说是这样

    就算把板子写法放在这也不知道板子怎么写啊,比如我就看了一个下午的板子也没看懂....

    板子题HDU4964:

    题意:

    在御坂网络里有N<=5*10^4个炮姐,之间存在单向的信息传递关系,构成一张M<=10^5的有向图 
    炮姐N是所有消息的源头 
    如果去掉炮姐b,炮姐N就无法给炮姐a传递信息,则称b是a的important sister 
    求每个炮姐的所有important sister的编号之和

    显然就是求从N号点出发到其他节点,对于每一个节点的支配点编号之和

    最后对于答案,求出每个点的支配点之后,从dfn编号小的递推至大的

    ·如果i == idom[i], ans[i] = i

    ·如果i != idom[i], ans[i] = ans[idom[i]] + i;

     

      1 #include<bits/stdc++.h>
      2 #define ll long long
      3 using namespace std;
      4 const int maxn = 100010;
      5 vector<int> suc[maxn];//后继 
      6 vector<int> pre[maxn];//前驱
      7 vector<int> dom[maxn];//支配点集合
      8 int fa[maxn];//节点在搜索树中的父亲
      9 int dfn[maxn];// 时间戳
     10 int semi[maxn];//dfn最小的半支配点 
     11 int idom[maxn];//dfn最大支配点 
     12 int anc[maxn];//并查集中的祖先 
     13 int best[maxn];//祖先链中dfn值最小的semi 
     14 int id[maxn];//id[x]时间戳为x的数量 
     15 ll sum[maxn];
     16 int n, m, num = 0; //num表示时间戳 
     17 
     18 inline void Restore_everything() {
     19     for(int i = 1; i <= n; ++i) {
     20         suc[i].clear();
     21         pre[i].clear();
     22         dom[i].clear();
     23         anc[i] = semi[i] = best[i] = i;
     24         sum[i] = dfn[i] = id[i] = fa[i] = idom[i] = 0;
     25     }
     26     num = 0;
     27 }
     28 
     29 inline void insert(int x, int y) {
     30     suc[x].push_back(y);
     31     pre[y].push_back(x);
     32 }
     33 
     34 oid dfs(int x) {
     35     dfn[x] = ++num, id[num] = x;
     36     for(int i = 0; i < suc[x].size(); ++i) {
     37         int to = suc[x][i];
     38         if(!dfn[to]) {
     39             dfs(to);
     40             fa[dfn[to]] = dfn[x];
     41         }
     42     }
     43 }
     44 
     45 int getfather(int x) {
     46     if(x == anc[x]) return x;
     47     int y = getfather(anc[x]);
     48     if(semi[best[x]] > semi[best[anc[x]]]) best[x] = best[anc[x]];
     49     return anc[x] = y;
     50 }
     51 
     52 inline void L_T() {
     53     for(int y = num; y > 1; --y) {
     54         int x = fa[y];
     55         for(int i = 0; i < pre[id[y]].size(); ++i) {
     56             int z = dfn[pre[id[y]][i]];
     57             if(!z) continue;
     58             getfather(z);
     59             semi[y] = min(semi[y], semi[best[z]]);
     60         }
     61         dom[semi[y]].push_back(y);
     62         anc[y] = x;
     63         for(int i = 0; i < dom[x].size(); ++i) {
     64             int z = dom[x][i];
     65             getfather(z);
     66             idom[z] = (semi[best[z]] < x) ? best[z] : x;
     67         }
     68         dom[x].clear();
     69     }
     70     for(int i = 2; i <= num; ++i) {
     71         if(idom[i] != semi[i]) idom[i] = idom[idom[i]];
     72         dom[idom[i]].push_back(i);
     73     }
     74     idom[1] = 0;
     75 }
     76 
     77 void calc(int x) {//calc是计算的意思 
     78     sum[id[x]] += id[x]; 
     79     for(int i = 0; i < dom[x].size(); ++i) {
     80         int y = dom[x][i];
     81         sum[id[y]] = sum[id[x]];
     82         calc(y);
     83     }
     84 } 
     85 
     86 int main() {
     87     while(~scanf("%d%d", &n, &m)) {
     88         Restore_everything();
     89         for(int i = 1; i <= m; ++i) {
     90             int x, y;
     91             scanf("%d%d", &x, &y);
     92             insert(x, y);
     93         }
     94         dfs(n);//题意要求,求从n号节点到其余节点的支配点数量 
     95         L_T();
     96         calc(1);
     97         for(int i = 1; i < n; ++i)
     98             printf("%lld ", sum[i]);
     99         printf("%lld
    ", sum[n]);
    100     }
    101     return 0;
    102 }

    最后感谢人民教师lyd前辈提供的板子!

  • 相关阅读:
    vue框架组件id获取
    Proxy 与 Object.defineProperty 优劣对比
    vue 父组件监听子组件生命周期
    Vue 的父组件和子组件生命周期钩子函数执行顺序
    k8s 集群部署--学习
    Linux命令:ipcs/ipcrm命令
    Python模块
    XAMPP+TestLink
    bug管理工具
    批量管理工具:pssh/ansible
  • 原文地址:https://www.cnblogs.com/ywjblog/p/9575502.html
Copyright © 2011-2022 走看看