Codechef GRAPHCNT 支配树学习及tarjan算法求解
来源:互联网 发布:linux命令强制关机 编辑:程序博客网 时间:2024/05/01 03:36
[Codechef GRAPHCNT]新年的有向图
【题目描述】
zlx同学在学习数论时,被虐了一脸,丧心病狂的他决定去报复社会。
zlx在公园里埋下N颗地雷,用来炸飞在春节期间秀恩爱的情侣。这N颗地雷由M条有向边连接成为一个有向图,zlx则在1号地雷处引爆1号地雷可以到达的地雷。现在,为了更好的实施这个计划,zlx需要知道存在多少对地雷(x,y),使得存在一条1到x和一条1到y的路径,这两条路径不经过同一个点(点1除外)。
【输入格式】
第一行为两个正整数N,M。
之后的M行,每行两个正整数v,u。代表存在一条v到u的有向边。
【输出格式】
输出有多少点对满足题目要求。
【样例输入】
6 61 21 31 42 52 63 6
【样例输出】
14
【提示】
对于30%的数据,图为有向无环图。
对于100%的数据,N<=100000,M<=500000
支配树简介
什么是支配树?支配树是什么?
对于一张有向图(可以有环)我们规定一个起点r(为什么是r呢?因为网上都是这么规定的),从r点到图上另一个点w可能存在很多条路径(下面将r到w简写为r->w)。
如果对于r->w的任意一条路径中都存在一个点p,那么我们称点p为w的支配点(当然这也是r->w的必经点),注意r点不讨论支配点。下面用idom[u]表示离点u最近的支配点。
对于原图上除r外每一个点u,从idom[u]向u建一条边,最后我们可以得到一个以r为根的树。这个树我们就叫它“支配树”。
相似
这个东西看上去有点眼熟?
支配点和割点(删掉后图联通块数增加)有什么区别?
我们考虑问题给定一个起点r和一个终点t,询问删掉哪个点能够使r无法到达t。
很显然,我们删掉任意一个r->t的必经点就能使r无法到达t,删掉任意一个非必经点,r仍可到达t。
从支配树的角度来说,我们只需删掉支配树上r到t路径上的任意一点即可
从割点的角度来说,我们是不是只需要考虑所有割点,判断哪些割点在r->t的路径上即可?是否将某个割点删掉即可让r无法到达t?
这当然是不正确的,我们可以从两个方面来说明它的错误:
- 删掉割点不一定使r无法到达t
这个图中点u是关键点(删掉后图联通块个数增加)
并且u在r->t的路径上,然而删掉点u后r仍然可以到达t - 图中不一定存在割点
在这个图中不存在任何割点
所以我们没有办法使用割点来解决这个问题。
简化问题
树
对于一棵树,我们用r表示根节点,u表示树上的某个非根节点。很容易发现从r->u路径上的所有点都是支配点,而idom[u]就是u的父节点。
这个可以在O(n) 的时间内实现。DAG(有向无环图)
因为是有向无环图,所以我们可以按照拓扑序构建支配树。
假设当前我们构造到拓扑序中第x个节点编号为u,那么拓扑序中第1 ~ X-1个节点已经处理好了,考虑所有能够直接到达点u的节点,对于这些节点我们求出它们在支配树上的最近公共祖先v,这个点v就是点u在支配树上的父亲。
如果使用倍增求LCA,这个问题可以在O((n+m) log2 n) 的时间内实现。
对于这两个问题我们能够很简便的求出支配树。
有向图
对于一个有向图,我们应该怎么办呢?
简单方法
我们可以考虑每次删掉一个点,判断哪些点无法从r到达。
假设删掉点u后点v无法到达,那么点u就是r->v的必经点(点u就是v的支配点)。
这个方法我们可以非常简单的在
其中
更快的方法
这里,我将介绍Lengauer-Tarjan算法。
这个算法能在很快的时间内求出支配树。
要介绍这个算法我们还需引入两个定理和一些概念
大概步骤
首先来介绍一些这个算法的大概步骤
- 对图进行DFS(深度优先遍历)并求出搜索树和DFS序。这里我们用
dfn[x] 表示点x 在dfs序中的位置。 - 根据半必经点定理计算出所有的半必经点作为计算必经点的根据
- 根据必经点定理修正我们的半必经点,求出支配点
半必经点
我们用idom[x]表示点x的最近支配点,用semi[x]表示点x的半必经点。
那什么是半必经点呢?
对于一个节点
Y ,存在某个点X 能够通过一系列点p i (不包含X 和Y )到达点Y 且∀i dfn[i]>dfn[Y] ,我们就称X 是Y 的半必经点,记做semi[Y]=X
当然一个点
对于每个点,我们只需要保存其半必经点中
我们可以更书面一点的描述这个定理:
- 对于一个节点
Y 考虑所有能够到达它的节点,设其中一个为X - 若
dfn[X]<dfn[Y],则 X 是Y 的一个半必经点 - 若
dfn[X]>dfn[Y] ,那么对于X 在搜索树中的祖先Z (包括X ),如果满足dfn[Z]>dfn[Y] 那么semi[Z] 也是Y 的半必经点
在这些必经点中,我们仅需要
这个半必经点有什么意义呢?
我们求出深搜树后,考虑原图中所有非树边(即不在树上的边),我们将这些边删掉,加入一些新的边
{(semi[w],w)|w∈V and w≠r} ,我们会发现构建出的新图中每一个点的支配点是不变的,通过这样的改造我们使得原图变成了DAG
是否接下来使用DAG的做法来处理就可以做到
必经点
一个点的半必经点有可能是一个点的支配点,也有可能不是。我们需要使用必经点定理对这个半必经点进行修正,最后得到必经点
对于一个点
X ,我们考虑搜索树上semi[X] 到X 路径上的所有点p0,p1,p2,p3...pk 。对于所有pi(1≤i<k) ,我们找出dfn[semi[ pi ]] 最小的一个 pi记为 Z
- 考虑搜索树上
X 与semi[X] 之间的其他节点(即不包含X 和semi[X] ),其中半必经点dfn 值最小的记为Z - 如果
semi[Z]=semi[X],则 idom[X]=semi[X] - 如果
semi[Z]≠semi[X] ,则idom[X]=idom[Z]
具体实现
对于求半必经点与必经点我们都需要处理一个问题,就是对于一个节点
对于
这样我们就能够在
代码
Codechef GRAPHCNT 的代码
#include <iostream>#include <cstdio>using namespace std;typedef long long lld;const int MaxN = 100000 + 10, MaxE = (5 * 100000) * 2 + MaxN;int head[MaxN], pre[MaxN], dom[MaxN], to[MaxE], nxt[MaxE], top;void addedge(int *h,int fr,int tt){ top ++; nxt[top] = h[fr]; to[top] = tt; h[fr] = top;}int n, m;void init(){ scanf("%d%d", &n, &m); int a, b; for(int i = 1; i <= m; ++i) { scanf("%d%d", &a, &b); addedge(head, a, b); addedge(pre, b, a); }}int bcj[MaxN], semi[MaxN], idom[MaxN], best[MaxN], dfn[MaxN], id[MaxN], fa[MaxN], dfs_clock;int push(int v){ if(v == bcj[v]) return v; int y = push(bcj[v]); if(dfn[semi[best[bcj[v]]]] < dfn[semi[best[v]]]) best[v] = best[bcj[v]]; return bcj[v] = y;}//带权并查集路径压缩void dfs(int rt){ dfn[rt] = ++dfs_clock; id[dfs_clock] = rt; for(int i = head[rt]; i; i = nxt[i]) if(!dfn[to[i]]) { dfs(to[i]); fa[to[i]] = rt; }}//求出dfs序void tarjan(){ for(int i = dfs_clock, u; i >= 2; --i) {//按dfs序从大到小计算 u = id[i]; for(int j = pre[u]; j; j = nxt[j])//semi { if(!dfn[to[j]]) continue; push(to[j]); if(dfn[semi[best[to[j]]]] < dfn[semi[u]]) semi[u] = semi[best[to[j]]]; } addedge(dom, semi[u], u); bcj[u] = fa[u];u = id[i - 1]; for(int j = dom[u]; j; j = nxt[j])//idom { push(to[j]); if(semi[best[to[j]]] == u) idom[to[j]] = u; else idom[to[j]] = best[to[j]]; } } for(int i = 2, u; i <= dfs_clock; ++i) { u = id[i]; if(idom[u] != semi[u]) idom[u] = idom[idom[u]]; }}int sons[MaxN];lld ans;void calc_son(){ for(int i = dfs_clock, u; i >= 2; --i) { u = id[i]; ++ sons[u]; if(idom[u] != 1) sons[idom[u]] += sons[u]; else ans -= ((lld)sons[u] * (lld)(sons[u] - 1)) / 2ll; }}void solve(){ for(int i = 1; i <= n; ++i) bcj[i] = semi[i] = best[i] = i; dfs_clock = 0; dfs(1); tarjan(); ans = ((lld)dfs_clock * (lld)(dfs_clock - 1)) / 2ll; calc_son(); cout << ans << endl;}int main(){ init(); solve(); return 0;}
- Codechef GRAPHCNT 支配树学习及tarjan算法求解
- CodeChef Graphcnt:Counting on a directed graph(支配树)
- 支配树 与 tarjan算法
- 快速构造支配树的Lengauer-Tarjan算法
- 康复计划#4 快速构造支配树的Lengauer-Tarjan算法
- 康复计划#4 快速构造支配树的Lengauer-Tarjan算法
- Lengauer-Tarjan算法--支配树构造(bzoj 2815: [ZJOI2012]灾难)
- 学习一个支配树
- 学习一个支配树
- 支配树学习笔记
- 支配树(Dominator tree)学习笔记 及HDU4694Important Sisters
- Tarjan算法求解最近公共祖先问题
- LCA 问题 用 Tarjan 离线算法 求解
- tarjan算法求解强连通分量
- 求解强联通分量 tarjan算法
- tarjan算法求解强连通分量
- Tarjan算法学习。
- 在流程图中求支配点的一种快速算法+[CodeChef FEB14]Graph Challenge解题报告(求半支配点)
- HDU 1973 Prime Path
- C++虚函数表解析
- LS10-linux时间编程之学习笔记
- Java从入门到精通—配置自己的环境变量
- PAT B1017. A除以B
- Codechef GRAPHCNT 支配树学习及tarjan算法求解
- TP5 二维码解码实现(php二维码识别)window系统
- UNP(卷2:进程间通信)—— 第7、8、9章:互斥锁、条件变量、读写锁、记录上锁
- Windows 10 16251 添加的 api
- Unreal Engine 4学习笔记:基础操作(复刻CS地图)
- saltstack自动化运维系列①之saltstack服务安装及简单使用
- saltstack自动化运维系列②之saltstack的数据系统
- saltstack自动化运维系列③之saltstack的常用模块使用
- saltstack自动化运维系列④之saltstack的命令返回结果mysql数据库写入