支配树学习笔记

我快菜死了!!!

我看了一天半的理论,又找了一下午的板子!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前辈提供的板子!

Published by

风君子

独自遨游何稽首 揭天掀地慰生平

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注