图论:处理最小环问题

最小环问题分析

前言

有向图的最小环和无向图的最小环是大不相同的!
有向图,可以 i -> j 然后 j -> i 就可以成环!所以在有向图中,两个节点即可成环。
但是无向图,就必须要三个点才能成环,所以我们分别对两种图做研究

无向图的最小环:Floyd算法的应用

用Floyd算法可以处理最小环问题,我们在枚举节点 i 和节点 j 之间的节点 k的之后,设定 dp[k][i][j] 是节点 i 到 j 经历 k 个节点的最短路,我们不妨设这条最短路已经知道了,也就是 i 、j 、k 是已知的,那么除去这三个点,假设k是环中的最大的点,那么剩下的点都是比 k小的,所以我们去枚举比 k小的点,假设 k 是与 i 和 j 相邻的点,那么除去这条路径之外,另外的所有路径中找到的最短路径就是最小环了!于是更新最小环如下:

int Solve(){
     
int ans = inf;
	for(int i = 1;i <= n;++i)
		for(int j = 1;j <= n;++j)
			dp[i][j] = graph[i][j];
	for(int k = 1;k <= n;++k)
	{
     
		for(int i = 1;i < k;++i)
			for(int j = 1;j < i;++j)
				if(ans > dp[i][j] + graph[j][k] + graph[k][i])
					ans = dp[i][j] + graph[j][k] + graph[k][i];
		for(int i = 1;i <= n;++i)
			for(int j = 1;j <= n;++j)
				if(dp[i][j] > dp[i][k] + dp[k][j])
					dp[i][j] = dp[i][k] + dp[k][j];
	}
	return ans;
}

相应习题:

HDOJ 1599 上面那个代码就是这个题的AC核心代码!

有向图的最短路:带权并查集 or 拓扑排序

分析:
拓扑排序是针对有向无环图的,只要检查入队列的总规模达到节点总个数了没有即可知道是不是有环图,因为当遇到有环图的那个环的时候,我们就找不到入度为0的点了,那么拓扑排序就无法进行,所有用拓扑排序可以很快的判断是否成环,复杂度很低:O(N + M)
拓扑排序的缺点:不晓得哪个环才是最小环,所有拓扑排序只能解决成环的问题,当然DFS也能解决成环的问题,两个点交替深搜即可!

拓扑排序找环代码:

int Sort(){
     
	int l = 0, r = 0;
	for(int i = 1;i <= n;i++){
     
		if(!in[i])
			q[r++] = i;//第一批入队 
	}
	while(l < r){
     
		int val = q[l++];// 出队列
		for(int i = 0;i < G[val].size();i++){
     
			in[G[val][i]]--;
			if(!in[G[val][i]])
				q[r++] = G[val][i];// 直接指向的点入队列 
		} 
	}// l < r 相当于队列不空 
	return r;// r != n的时候就是存在环路
} 

真正解决最小环:带权并查集

例题:洛谷 2661 信息传递

算法分析:

当两个人可以连接的时候,他们所在的两棵树的信息实际上都连接过去了!但是两个人:x和y的连接,实际上他们的权重只有1,因为只传递了一次信息
也就是:dis(x, y) = 1;
所以推出根节点的传递更新公式:
dis(px, py) = dis(y, py) - dis(x, px) + 1;
注意,我这个状态的设计:dis(x, y):是x把信息传递给y的状态变化,所以很明显这是有向图,求环的最小规模
当并查集检查的时候,发现是来自同一棵树,然后二次相连了,树是极大无环图,如果多加一条边,就是成环图了,所以这个时候就是a那棵树的传递次数+b那棵树的传递次数+当前传递的这一次

AC代码:

#include 

const int maxn = 2e5 + 5;
int n, a, p[maxn], dis[maxn], ans = 0x3f3f3f3f;

int find(int x){
     
	if(x == p[x])	return x;
	int r = p[x]; p[x] = find(p[x]);
	dis[x] += dis[r];
	return p[x];
}

void merge(int x, int y){
     
	int px = find(x), py = find(y);
	if(px != py){
     
		p[px] = py;
		dis[px] = dis[y] - dis[x] + 1;
	}
	else{
     
		if(ans > dis[x] + dis[y] + 1)
			ans = dis[x] + dis[y] + 1;
	}
}

int main(){
     
	scanf("%d", &n);
	for(int i = 1;i <= n;i++)
		p[i] = i;
	for(int i = 1;i <= n;i++){
     
		scanf("%d", &a);
		merge(i, a);
	}
	printf("%d", ans);
	return 0;
}

你可能感兴趣的:(算法)