割点和割边

割点

定义:在一个连通分量中存在一些关键点,如果删除它,会把这个连通分量分成两个或多个,这种点称为割点。
在一个连通分量G中,对任意一点,做DFS,能够访问到所有点,产生一棵“深搜优先生成树”T。
定理1:T的根节点s是割点,当且仅当s有两个或两个以上的孩子节点。
定理2:T中非根节点u是割点,当且仅当u存在一个孩子节点v,v及其后代都没有回退边连回u的祖先。
编程实现定理2:
设u的一个直接后代是v
1) num[u], 记录DFS对每个节点的访问顺序。
2) low[v], 记录v和v的后代能够连回的祖先num。
3) 如果low[v] >= num[u], 说明v及v的后代都没有回退边回到u的祖先,那么u就是一个割点。
4) 搜完后还要对根节点特判是否是割点。
代码实现:
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <vector>
#include <cstring>
#include <set>
#define ll long long
const int mod = 1e9+7;

using namespace std;

vector<int>edge[1005];
int vis[1005],low[1005],num[1005],gd[1005],n,sum;
void dfs(int s,int f)
{
    int cnt = 0;
    sum++;
    low[s] = num[s] = sum;//赋值,low的初值就是num
	int l = edge[s].size();
	for(int i = 0; i < l; i++){
		int v = edge[s][i];
		if(!num[v]){
			cnt++;//当前节点的孩子个数
			vis[v] = 1;
			dfs(v,s);
			low[s] = min(low[s],low[v]);//用后代更新low值。 
			if(low[v] >= num[s] && s != 1) gd[s] = 1;
		}
		else if(num[v] < num[s] && v != f) low[s] = min(low[s],num[v]);
                //判断回退边,如果回退边的点的序号比s小,那么说明v是s的祖先,更新low[s].
	}
	if(s == 1 && cnt >= 2){
		gd[s] = 1;//特判根节点
	}
}
 
int main()
{
	while(~scanf("%d",&n) && n){
		sum = 0;
		int t,u,v;
		memset(vis,0,sizeof(vis));
		memset(low,0,sizeof(low));
		memset(gd,0,sizeof(gd));
		memset(num,0,sizeof(num));
		while(scanf("%d",&t) && t){
			char c;
			while(1){
	    		scanf("%d%c",&v,&c);
	    		edge[t].push_back(v);
	    		edge[v].push_back(t);
	    		if(c == '\n') break;
	    	}
		}
		dfs(1,-1);
		int ans = 0;
		for(int i = 1; i <= n; i++){
			if(gd[i]) ans++;
		}
		printf("%d\n",ans);
		for(int i = 1; i <= n; i++) edge[i].clear();
	}
	return 0;
}
注意:如果想用vis判断某个点是否已经走过,那么vis要写在for循环外面,不能写在for的if判断里面。

巧妙的判断s和t之间的关键点方法
从起点s到终点t的路径数等于经过点v的次数,那么点v就是连接s和v的关键点,如果删除这个点,那么s和t不能连通。很好理解,也就是说s到t的所有路径都要经过点v,没有点v这些路径都不能够连接到t点。

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <vector>
#include <cstring>
#include <set>
#define ll long long
const int mod = 1e9+7;

using namespace std;

vector<int>edge[2005];
int vis[1005],lz = 0,num[1005];
int n,m;
void dfs(int s,int t)
{
	if(s == t){
		lz++;
		for(int i = 1; i <= n; i++){
			if(vis[i]){
				num[i]++;
			}
		}
		return;
	}
	vis[s] = 1;
	int l = edge[s].size();
	for(int i = 0; i < l; i++){
		int v = edge[s][i];
		if(!vis[v]){
			dfs(v,t);
		}
	}
	vis[s] = 0;
}

int main()
{
    int u,v;
    scanf("%d%d",&n,&m);
    for(int i = 1; i <= m; i++){
    	scanf("%d%d",&u,&v);
    	edge[u].push_back(v);
    	edge[v].push_back(u);
	}
	scanf("%d%d",&u,&v);
	dfs(u,v);
	int cnt = 0;
	for(int i = 1; i <= n; i++){
		if(i != u && i != v){
			if(num[i] == lz) cnt++;
		}
	}
	if(cnt) printf("%d\n",cnt);
	else printf("-1\n");
	return 0;
}

割边

定义:在一个连通分量中删除一条边,把这个连通分量分成了两个,这个边称为割边。
改为:
if(low[v] > num[s] && s != 1)
其他程序不变,可算出割边数量。





全部评论

相关推荐

06-03 15:32
点赞 评论 收藏
分享
代码飞升:别用口语,后端就写后端,前端就写前端,最后别光后悔
点赞 评论 收藏
分享
评论
点赞
收藏
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务