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