首页 > 代码库 > {part1}DFN+LOW(tarjan)割点
{part1}DFN+LOW(tarjan)割点
什么是jarjan?
1)求割点
定义:在无向连通图中,如果去掉一个点/边,剩下的点之间不连通,那么这个点/边就被称为割点/边(或割顶/桥)。
意义:由于割点和割边涉及到图的连通性,所以快速地求出割点和割边对于解决有关图连通性的问题有很大的帮助。
首先我们可以知道这个问题的上界为O(n*(n+m))/O(m*(n+m)),通过O(n)/O(m)枚举去掉的点/边,然后BFS在O(n+m)检查剩下的点的连通性就可以得到一个平方级别的算法。
这个算法显然难以进行优化,所以我们考虑从图本身的结构入手。
如果我们对一个无向连通图进行DFS,将一个点的前驱视为它的父亲,那么就可以构造出一个DFS树,一个无向图的DFS树可能因枚举边的顺序不同而有多种形态,比如一个三个点的完全图,从1开始DFS,如果先枚举从1到2的边,那么DFS树就是1-2-3,如果先枚举从1到3的边,那么DFS树就是1-3-2。
在构造出一棵DFS之后,无向图中的边就被划分成了两类:属于树的边(以下简称树边或叫父子边)以及不在树中的边(简称非树边或返祖边)。
如下图所示:
父子边用黑色标记,返祖边用红色标记
性质1:非树边连接的一定是一个点和它在DFS树中的祖先。
这条性质的意义是在DFS树中一个点的各个子树中的结点之间不会存在边,我们可以用“显然法”来证明这一性质,或者用下一段中的反证法:
如果对于点w的两个儿子u和v,如果以u为根的子树中有一个结点x有连向以v为根的子树中的y的边,假设枚举w的儿子的时候先访问到了u,那么在DFS到x时y一定也会被访问到,那么x就变成了y的父亲,从y也能访问到v,y就变成了v的祖先,这与w是v在DFS树中的父亲相矛盾。
接下来我们分开来讨论,先讨论如何判断一个点是否为割点,割边和割点稍微有些不同。
去掉一个结点x可能造成的不连通情况为:它的父亲与儿子不连通,或者它的儿子与另一个儿子不连通。如果出现第一种情况,那么说明在它的某一个儿子所在的子树中没有连向x的祖先的返祖边。如果没有出现第一种情况而出现第二种情况,那么说明它有大于一棵子树,而且所有子树都满足上述条件(如果出现有返祖边的子树,那么这棵子树与x的父亲连通,那么另一棵子树与这棵子树不连通的同时也会出现第一种情况),但是有这样的子树就代表出现了第一种情况,所以这种情况一定是在x没有父亲,也就是x是根节点时出现。
现在问题就变成了如何判定x的一棵子树中是否有连向x的祖先的返祖边,然后记录这样的子树的个数就好了。
我们用DFN数组记录dfs中每个结点的访问时间(访问序号),如果出现返祖边(u,v),那么DFN[v]肯定小于DFN[u],因为访问v是在访问u之前,不然v就不是u的祖先了。
然后用low数组记录dfs中每个点沿着除了父亲之外的所有点能访问到的最小的dfn值,那么对于x的一个儿子y,如果low[y]>=dfn[x],那么说明y中不存在连向x的祖先的返祖边,同时因为low[y]!=dfn[x](不能访问父亲),而且y不能访问到x的其他子树中,这些子树中的结点的dfn值在(dfn[x],dfn[y])这个区间内,所以low[y]>=dfn[x]可以改写为low[y]>=dfn[y],由于low值初值就被赋为与dfn相等,所以条件就是low[y]==dfn[y]。
每个点左边是dfn值,右边是low值。(经过返祖边后则停止)
那么我们就可以用一次DFS求出所有割点了:对于dfs中的每个结点x,枚举它的相邻结点y:
1.如果y是x的父亲,那么跳过(不访问父结点);
2.如果y已经被访问过,那么这条边是非树边,即返祖边,用dfn[y]更新low[x];
3.如果y没有被访问,那么对y进行dfs,用low[y]更新low[x],并判断low[y]是否等于dfn[y],如果是,那么x的这样的子树个数+1。
对于以x为根的子树的DFS结束之后,判断x是否为割点,如果x不是根节点,那么如果这样的子树个数不为零,x就是割点(去掉之后父亲与子树不连通),如果x是根节点,那么x需要有两个及以上这样的子树。
例题
n个城市之间有通讯网络,每个城市都有通讯交换机,直接或间接与其它城市连接。因电子设备容易损坏,需给通讯点配备备用交换机。但备用 交换机数量有限,不能全部配备,只能给部分重要城市配置。于是规定:如果某个城市由于交换机损坏,不仅本城市通讯中断,还造成其它城市通讯中断,则配备备 用交换机。请你根据城市线路情况,计算需配备备用交换机的城市个数,及需配备备用交换机城市的编号。
第一行,一个整数n,表示共有n个城市(2<=n<=20000)
下面有若干行(<=60000):每行2个数a、b,a、b是城市编号,表示a与b之间有直接通讯线路。
7
1 2
2 3
2 4
3 4
4 5
4 6
4 7
5 6
6 7
第一行,1个整数m,表示需m个备用交换机,下面有m行,每行有一个整数,表示需配备交换机的城市编号,输出顺序按编号由小到大。如果没有城市需配备备用交换机则输出0。
2
2
4
一个单纯地求割点。
cpp:
1 #include<iostream> 2 #include<string> 3 #include<algorithm> 4 #include<cstring> 5 #include<cstdio> 6 #include<cmath> 7 #include<iomanip> 8 #include<queue> 9 #include<vector>10 #include<ctime>11 using namespace std;12 const int maxn=500001;13 int n;14 int len=0,linkk[maxn];15 int ans[maxn],ind=0,top=0;16 int dfn[maxn],low[maxn];17 int root=0;18 struct node19 {20 int x,y;21 }e[maxn];22 23 #define up(i,j,N) for(int i=j;i<=n;i++)24 #define Auto(i,z) for(int i=linkk[z];i;i=e[i].y)25 26 void init(int xx,int yy)27 {28 e[++len].y=linkk[xx];linkk[xx]=len;29 e[len].x=yy;30 }31 32 void dfnlow(int x)33 {34 dfn[x]=low[x]=++ind;35 int son=0,y;36 for(int i=linkk[x];i;i=e[i].y)37 {38 y=e[i].x;39 if(!dfn[y])40 {41 dfnlow(y);42 ++son;43 if(low[y]<low[x])44 {45 low[x]=low[y];46 }47 if((x==root&&son>1)||(x!=root&&low[y]>=dfn[x]))48 if(!ans[x])49 ans[x]=1,top++;50 }51 else if(low[x]>dfn[y])52 low[x]=dfn[y];53 }54 }55 56 int main()57 {58 /*freopen("2.in","r",stdin);59 freopen("2.out","w",stdout);*/60 //ios::sync_with_stdio(false);61 cin>>n;62 int x,y;63 while(cin>>x>>y)64 {65 init(x,y);66 init(y,x);67 }68 up(i,1,n)69 {70 if(!dfn[i])71 {72 root=i;73 dfnlow(i);74 }75 }76 cout<<top<<endl;77 for(int i=1;i<=n;i++)78 if(ans[i]==1)79 cout<<i<<" ";80 cout<<endl;81 return 0;82 }
{part1}DFN+LOW(tarjan)割点