设为首页 加入收藏

TOP

割点(一)
2019-09-03 03:37:22 】 浏览:54
Tags:割点

INTRODUCTION:

在一个无向图中,如果有一个顶点集合,删除这个顶点集合以及这个集合中所有顶点相关联的边以后,图的连通分量增多,就称这个点集为割点集合
如果某个割点集合只含有一个顶点X(也即{X}是一个割点集合),那么X称为一个割点。--百度百科

首先,什么是割点?

在一个有N个节点,M条边的有向图中,若删去一个点,以及所有与这个节点直接相连的边会使该图不连通、或出现更多互不连通的子图(原图本身就不连通的情况),则称这个点为割点。

那么不难想到割点的一种求法:

一、暴力枚举:

枚举每一个节点,判断该节点是否是割点(没有什么是暴力解决不了的

不过显然暴力枚举太慢了,出题人也不想让你这么轻松就AC

因此,可以用以下两种方法快速的求出割点:

二、DFS树:

首先来设想一下,假如我们用DFS来遍历一张无向图连通图,保证每个点只被遍历到一次,然后将遍历时经过的每一个点,每一条边取出,组成一个新的联通图,显而易见:这张新图必然是一棵树。

我们称这棵树为DFS树,同时不难看出,由于遍历时所选的根节点不同,遍历的顺序不同,所以这颗DFS树并不唯一,不过这对于求割点而言影响不大,所以只要任意求出一颗DFS树就可以了

对于原图而言,我们将构成DFS树的边称为树边,不属于DFS树的边称为非树边

存在一个结论:对于每一条非树边,他只可能连接DFS树上某个节点和他的祖先,不可能连接两个分别位于不同子树上的节点,我们称这样连接DFS树上的某个节点与他的祖先的非树边返祖边。

如图所示,只存在形如边I的返祖边,不存在形如边J的横跨边

图1证明:若存在边J,则在DFS时必然会先经过边J由5节点遍历到3节点,形成如下图形,不可能会形成一条横跨边。

图2:借助DFS树的这些性质,我们就可以求割点了:

分三种情况讨论:

1.若该节点是叶子节点,那么他一定不是割点

2.若该节点是根节点,那么若他的子树数量大于等于2,则他是割点,若他只有一颗子树,则他不是割点

3.若该节点既不是根节点也不是叶子节点,则若他的每一颗子树都中存在一条返祖到他的祖先节点(不包括)的返祖边,则他不是割点(如上图2中的2、5节点)反之他是割点

如图所示:图A中的1节点是一个割点,4节点是一个割点,图B中的1节点不是一个割点,5节点不是一个割点

代码如下:

  1 #include<cstdio>
  2 #include<cstring>
  3 #include<iostream>
  4 #include<algorithm>
  5 using namespace std;
  6 const int maxn = 100010;
  7 const int maxm = 500005;
  8 int n, m;
  9 int tot, ans;
 10 bool vis[maxn];
 11 struct edge
 12 {
 13     int to;
 14     int next;
 15     bool t;//这条边是否是一条从子节点到父节点的边
 16     bool flag;//是否是树边
 17 }e[maxm];
 18 struct node
 19 {
 20     int f;//该节点的父节点
 21     int to;//该节点及其子树的所有节点的返祖边能够到达的最浅深度
 22     int son;//该节点的子节点的个数
 23     int deep;//该节点的深度
 24     int head;
 25     bool root;//是否是根节点
 26     bool flag;//是否是割点
 27 }p[maxn];
 28 void add(int u, int v)
 29 {
 30     tot++;
 31     e[tot].to = v;
 32     e[tot].next = p[u].head;
 33     p[u].head = tot;
 34 }
 35 void dfs(int u, int f)//u代表当前节点,f代表该节点的父节点
 36 {
 37     vis[u] = 1;
 38     p[u].deep = p[f].deep + 1;//记录深度
 39     p[u].to = p[u].deep;//初始设该节点及其子树所能够连接的最浅深度为该节点的深度
 40     for (int i = p[u].head; i; i = e[i].next)//枚举该节点的所有子节点
 41     {
 42         int v = e[i].to;
 43         if (!vis[v])
 44         {
 45             p[u].son++;//记录子节点的数量
 46             p[v].f = u;//记录u的子节点的父节点为u
 47             e[i].flag = 1;//这条边属于树边
 48             dfs(v, u);
 49         }
 50     }
 51 }
 52 void init(int u)//处理每个节点的子树
 53 {
 54     for (int i = p[u].head; i; i = e[i].next)//更新每一个节点的子树所能抵达的最浅深度
 55     {
 56         int v = e[i].to;
 57         if (e[i].flag && !e[i].t)
 58         {
 59             init(v);
 60             p[u].to = min(p[u].to, p[v].to);
 61         }
 62     }
 63 }
 64 bool check(int u)
 65 {
 66     for (int i = p[u].head; i; i = e[i].next)
 67     {
 68         if (e[i].flag && !e[i].t)
 69         {
 70             int v = e[i].to;
 71             if (!(p[v].to < p[u].deep))
 72                 return 0;
 73         }
 74     }
 75     return 1;
 76 }
 77 void work()//判断每一个节点是否是割点
 78 {
 79     for (int u = 1; u <= n; u++)
 80     {
 81         if (u == 1)//u是根节点
 82         {
 83             if (p[u].son <= 1)
 84                 p[u].flag = 1;//不是割点
 85         }
 86         else if (p[u].son == 0)
 87             p[u].flag = 1;
 88         else
 89         {
 90             if (check(u))
 91                 p[u].flag = 1;
 92         }
 93     }
 94 }
 95 int main()
 96 {
 97     cin >> n >> m;
 98     for (int i = 1; i <= m; i++)
 99     {
100         int u, v;
101         cin >> u >> v;
102         add(u, v);
103         add(v, u);
104     }
105     dfs(1, 0);
106     //处理最小深度
107     //注意要特判从儿子节点到父节点的边(树边的一半),否则所有节点(都被认为可以到达根节点)
108     for (int u = 1; u <= n; u++)
109     {
110         for (int i = p[u].head; i; i = e[i].next)
111         {
112             int v = e[i].to;
113             if (!e[i].flag && v != p[u].f)//若这条边是树边且不是由儿子节点到父节点
114             {
115                 p[u].to = min(p[u].to, p[v].deep);//更新u能抵达的最前深度
1
首页 上一页 1 2 下一页 尾页 1/2/2
】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
上一篇洛谷 P1101单词方阵 下一篇Data-Structure-Notes

最新文章

热门文章

Hot 文章

Python

C 语言

C++基础

大数据基础

linux编程基础

C/C++面试题目