求图上最小环

先介绍求类似与基环树上的环,每个点只有一条出边的环,:
这里以这道题P2661 [NOIP2015 提高组] 信息传递为例子
1dfs/bfs O(n)

#include<bits/stdc++.h>
using namespace std;
const int N=2e5+7;
int now,ans;
int a[N],b[N],f[N]; 
void dfs(int x,int d)
{
	if(x==now)
	{
		ans=min(d,ans);
		return ;
	}
	if(a[x]||b[x]) return ;
	a[x]=1;
	dfs(f[x],d+1);
	a[x]=0;
}
int main()
{
	int n;
	scanf("%d",&n);
	for(int i=1;i<=n;i++) scanf("%d",&f[i]);
	ans=0x3f3f3f3f;now=0;
	for(now=1;now<=n;now++)
	{
		dfs(f[now],1);
		b[now]=1;
	}
	printf("%d\n",ans);
}

2.tarjan O(n)

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N = 4e5+7, M = 4e5+7;
int n, m;
int h[N], e[M], ne[M], idx;
int dfn[N], low[N], timestamp;
int stk[N], top;
bool in_stk[N];
int id[N], scc_cnt, Size[N];
int dout[N];

void add(int a, int b)
{
    e[idx] = b, ne[idx] = h[a], h[a] = idx ++ ;
}

void tarjan(int u)
{
//	printf("%d\n",u);
    dfn[u] = low[u] = ++ timestamp;
    stk[ ++ top] = u, in_stk[u] = true;
    for (int i = h[u]; i != -1; i = ne[i])
    {
        int j = e[i];
        if (!dfn[j])
        {
            tarjan(j);
            low[u] = min(low[u], low[j]);
        }
        else if (in_stk[j]) low[u] = min(low[u], dfn[j]);
    }

    if (dfn[u] == low[u])
    {
        ++ scc_cnt;
        int y;
        do {
            y = stk[top -- ];
            in_stk[y] = false;
            id[y] = scc_cnt; 
            Size[scc_cnt] ++ ;
        } while (y != u);
    }
}
int main()
{
	memset(h, -1, sizeof h);
	int n;
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
	{
		int x;scanf("%d",&x);
		add(i,x);
	}
	for(int i=1;i<=n;i++) if(!dfn[i]) tarjan(i);
//	printf("%d\n",scc_cnt);
//	for(int i=1;i<=n;i++) printf("i:%d %d\n",i,low[i]);
	int ans=0x3f3f3f3f;
	for(int i=1;i<=scc_cnt;i++) if(Size[i]>1) ans=min(ans,Size[i]);
	printf("%d\n",ans);
}

3.带权并查集 O(n)
就是维护并查集中点到根的距离,然后如果发现连个点在同一集合中,环的大小就是abs(d[i]-d[j])+1
并查集不好记录你求出来的环上的点有哪些
4.拓扑排序 O(n)可以解决无向图上的简单环的问题
对无向图进行拓扑排序,剩下的点就是一个强连通分量
有可能出现一个点在多个环上的时候:
5.最短路相关
(1)FloydO(n^3)
对于无向图是min(d[i,j]+a[j,k]+a[k,i]),对于有向图则直接求自己到自己的最短距离
ACwing344. 观光之旅

#include<bits/stdc++.h>
#define ll long long
#define pb push_back
using namespace std;
const int N=307;
int a[N][N],d[N][N],pos[N][N];
int ans;
vector<int> pa;
void getpa(int i,int j)
{
	if(pos[i][j]==0) return;
	getpa(i,pos[i][j]);
	pa.pb(pos[i][j]);
	getpa(pos[i][j],j);
}
int main()
{
	int n,m;
	scanf("%d%d",&n,&m);
	memset(a,0x3f,sizeof(a));
	for(int i=1;i<=n;i++) a[i][i]=0;
	for(int i=1;i<=m;i++)
	{
		int u,v,w;
		scanf("%d%d%d",&u,&v,&w);
		a[u][v]=a[v][u]=min(a[u][v],w);
	}
	memcpy(d,a,sizeof(a));
	ans=0x3f3f3f3f;
	for(int k=1;k<=n;k++)
	{
		for(int i=1;i<k;i++)
		{
			for(int j=i+1;j<k;j++)
			{
				if((ll)d[i][j]+a[j][k]+a[k][i]<ans)
				{
					ans=d[i][j]+a[j][k]+a[k][i];
				// 	printf("i:%d j:%d k:%d ans:%lld\n",i,j,k,ans);
					pa.clear();
					pa.pb(i);
					getpa(i,j);
					pa.pb(j);
					pa.pb(k);
				}
			}
		}
		for(int i=1;i<=n;i++)
		{
			for(int j=1;j<=n;j++)
			{
				if(d[i][j]>d[i][k]+d[k][j])
				{
					d[i][j]=d[i][k]+d[k][j];
					pos[i][j]=k;
				}
			}
		}
	}
	if(ans==0x3f3f3f3f) printf("No solution.\n");
	else
	{
		for(int i=0;i<pa.size();i++) printf("%lld ",pa[i]);
		printf("\n");
	}
}

(2)dij
O(n^2logn) 的思路
1-n对所有点跑dij,每次跑完一个点,将他的dis置为无穷,再次跑到它时就得到经过它的最小环 这个不好跑无向图

O(n^2logn)的思路
每次断掉一个点的所有入边,对于剩下的图跑dij,此时得到的最小环就是(u,v)+dis

### 有向中的最小环算法 在有向中寻最小环(即权重之和最小的环)是一个常见的图论问题,广泛应用于网络分析、路径优化等领域。针对有向最小环问题,可以采用多种方法进行解,包括基于 **Floyd-Warshall 算法** 的改进方法、**深度优先搜索(DFS)** 以及 **Bellman-Ford 算法** 的变种等。 #### 使用 Floyd-Warshall 算法最小环 Floyd-Warshall 算法是一种经典的动态规划算法,用于计算所有点对之间的最短路径。在有向中,可以通过修改该算法来寻最小环。 算法步骤如下: 1. 初始化距离矩阵 `dis[i][j]`,其中 `dis[i][j]` 表示从点 `i` 到点 `j` 的最短路径。 2. 对于每个中间点 `k`,更新所有点对 `(i, j)` 的最短路径。 3. 在每次更新前,检查是否存在环:`dis[i][j] + map[j][k] + map[k][i]`,如果该值小于当前最小环,则更新最小环。 ```python # Floyd-Warshall 算法扩展用于最小环 def find_min_cycle(n, graph): import sys INF = sys.maxsize dis = [[INF] * n for _ in range(n)] for i in range(n): for j in range(n): dis[i][j] = graph[i][j] min_cycle = INF for k in range(n): for i in range(n): for j in range(n): if dis[i][j] + graph[j][k] + graph[k][i] < min_cycle: min_cycle = dis[i][j] + graph[j][k] + graph[k][i] # 更新最短路径 for i in range(n): for j in range(n): if dis[i][j] > dis[i][k] + dis[k][j]: dis[i][j] = dis[i][k] + dis[k][j] return min_cycle if min_cycle != INF else -1 ``` 该方法的时间复杂度为 $ O(n^3) $,适用于节点数较少的[^2]。 #### 深度优先搜索(DFS)方法 对于稀疏,可以采用深度优先搜索的方式遍历,记录访问路径,当发现回边时判断是否形成环,并计算环的权重和。 ```python def dfs_cycle_detection(graph, visited, path, node, start, min_cycle): visited[node] = True path.append(node) for neighbor in range(len(graph)): if graph[node][neighbor] != 0: if not visited[neighbor]: dfs_cycle_detection(graph, visited, path, neighbor, start, min_cycle) elif neighbor == start and len(path) > 1: cycle_weight = sum(graph[path[i]][path[(i+1) % len(path)]] for i in range(len(path))) if cycle_weight < min_cycle[0]: min_cycle[0] = cycle_weight path.pop() visited[node] = False def find_min_cycle_dfs(graph): n = len(graph) visited = [False] * n min_cycle = [float('inf')] for i in range(n): dfs_cycle_detection(graph, visited, [], i, i, min_cycle) return min_cycle[0] if min_cycle[0] != float('inf') else -1 ``` 该方法适用于稀疏,但在稠密中效率较低。 #### Bellman-Ford 算法扩展 Bellman-Ford 算法可以检测中的负权环,也可以扩展用于寻最小正权环。通过多次松弛操作,可以判断是否存在环并计算最小环的权重。 ```python def bellman_ford_min_cycle(graph, n): min_cycle = float('inf') for s in range(n): dist = [float('inf')] * n dist[s] = 0 for _ in range(n-1): for u in range(n): for v in range(n): if graph[u][v] != float('inf') and dist[u] + graph[u][v] < dist[v]: dist[v] = dist[u] + graph[u][v] # 检查环 for u in range(n): for v in range(n): if graph[u][v] != float('inf') and dist[u] + graph[u][v] < dist[v]: cycle_len = graph[u][v] current = u while True: next_node = -1 for i in range(n): if graph[current][i] != float('inf') and dist[current] + graph[current][i] == dist[i]: next_node = i break if next_node == -1 or current == u: break cycle_len += graph[current][next_node] current = next_node min_cycle = min(min_cycle, cycle_len) return min_cycle if min_cycle != float('inf') else -1 ``` 该方法适用于存在负权边的,但实现较为复杂。
评论 2
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值