Given a graph, find the cycle with \(n\) nodes that has the minimum sum of edge weights \((n\ge 3)\).
The minimum cycle in a graph is also known as the girth.
Process
Brute Force Solution
Let there be an edge between \(u\) and \(v\) with weight \(w\), and let \(dis(u,v)\) denote the shortest path between \(u\) and \(v\) after removing the edge between \(u\) and \(v\).
Then the minimum cycle in an undirected graph is \(dis(u,v)+w\).
Note that if finding the minimum cycle in a directed graph, the corresponding formula should be modified: the minimum cycle is \(dis(v,u)+w\).
Let \(val\left(u,v\right)\) denote the edge weight between \(u\) and \(v\) in the original graph.
We note a property of the Floyd algorithm: when the outermost loop reaches point \(k\) (before starting the \(k\)-th iteration), the shortest path array \(dis\) satisfies that \(dis_{u,v}\) represents the shortest path from \(u\) to \(v\) using only points with indices in \(\left[1, k\right)\).
By the definition of a minimum cycle, it has at least three vertices. Let \(w\) be the vertex with the largest index in the cycle, and let \(u\) and \(v\) be the two points adjacent to \(w\) on the cycle. Then when the outermost loop enumerates \(k=w\), the length of the cycle is \(dis_{u,v}+val\left(v,w\right)+val\left(w,u\right)\).
Therefore, during the loop, for each \(k\), enumerate \((i,j)\) satisfying \(i<k,j<k\) and update the answer.
Recording the Path
Now we know the cycle is of the form \(u\to k\to v\), and then from \(v\) back to \(u\) (all points visited have indices \(<k\)).
The problem reduces to finding the path \(v\leadsto u\). By the triangle inequality \(dis_{u,v}\le dis_{u,i}+dis_{i,v}\), consider recording \(pos_{u,v}=j\) as the point such that \(dis_{u,v}=dis_{u,j}+dis_{j,v}\). Clearly, \(j\) lies on the path \(v\leadsto u\).
Thus, the path can be split into two segments: \(v\leadsto j\) and \(j\leadsto u\), which can be handled recursively.
Proof that recursion does not fall into an infinite loop
Proof by contradiction.
Assume the cycle passes through a point \(u\) repeatedly. Then there must be an edge starting from \(u\) that, after traversing several edges, returns to \(u\). This forms a new cycle.
Since the graph contains no negative cycles (if there were a negative cycle, there would be no minimum cycle), the sum of edge weights of the new cycle must be less than or equal to that of the original cycle.
Therefore, we can simply take this new cycle, which does not pass through \(u\) repeatedly. The assumption is false, so the cycle does not pass through any point repeatedly.
Thus, when recursively processing points \(u\) and \(v\), their \(pos_{u,v}\) must not equal the indices of the two points, meaning a new point is added.
In particular, when \(u\) and \(v\) are adjacent, return directly.
Since the total number of points is \(n\), the number of additions (i.e., recursive calls) does not exceed \(n\), so recursion will not fall into an infinite loop.
Properties
Time complexity: \(O(n^3)\).
Implementation
Below are C++ and Python reference implementations (with path recording):
// The graph has n nodesintval[MAXN+1][MAXN+1];// Adjacency matrix of the original graphintcnt,path[MAXN+5];// Record the path and length of the minimum cyclevoidget_path(intu,intv){// Get the path between u and vif(pos[u][v]==0)return;intk=pos[u][v];get_path(u,k);path[++cnt]=k;get_path(k,v);}voidFloyd(constint&n){staticintdis[MAXN+1][MAXN+1];// Shortest path matrixstaticintpos[MAXN+1][MAXN+1];memcpy(dis,val,sizeof(val));memset(pos,0,sizeof(pos));for(intk=1;k<=n;++k){for(inti=1;i<k;++i)for(intj=1;j<i;++j)if(ans>(longlong)val[i][k]+val[k][j]+dis[i][j]){// Found a shorter cycle// Since j<i<k is guaranteed here, the three points are distinct, avoiding zero-length cycles.ans=val[i][k]+val[k][j]+dis[i][j],cnt=0;path[++cnt]=i,path[++cnt]=k,path[++cnt]=j;// Add i, k, j in orderget_path(j,i);// Add the path from j to i}for(inti=1;i<=n;++i)// Normal Floyd update for shortest pathsfor(intj=1;j<=n;++j){if(dis[i][j]>dis[i][k]+dis[k][j]){dis[i][j]=dis[i][k]+dis[k][j];pos[i][j]=k;// The current path can be updated via k}}}}
# Define a sufficiently large value to represent infinityINF=sys.maxsizedefget_path(i,j,pos,path,cnt):""" Recursively get the intermediate nodes on the shortest path from node i to node j. Args: i (int): Starting node (0-based index). j (int): Ending node (0-based index). pos (list[list[int]]): Matrix recording intermediate nodes on shortest paths. pos[i][j] = k means the shortest path from i to j passes through k. path (list[int]): List storing path nodes (using 0-based index). cnt (int): Current number of path nodes. Returns: int: Updated number of path nodes. """# If pos[i][j] is -1, there is no intermediate node between i and jifpos[i][j]==-1:returncnt# Get intermediate node kk=pos[i][j]# Recursively get the path from i to kcnt=get_path(i,k,pos,path,cnt)# Add intermediate node k to the pathpath[cnt]=kcnt+=1# Recursively get the path from k to jcnt=get_path(k,j,pos,path,cnt)returncntdeffind_minimum_cycle_undirected(n,edges):""" Find the minimum cycle in an undirected graph using the Floyd-Warshall algorithm. Args: n (int): Number of nodes in the graph (1 to n). edges (list[tuple]): List of edges, each element is (u, v, w), indicating an edge between nodes u and v with weight w. Node indices are 1 to n. Returns: tuple: Contains the length of the minimum cycle and the path. If no cycle exists, return (INF, []). The path is a list of node indices (1-based index). """# Use 0-based indexing internallyN=n# Initialize adjacency matrix g, representing original edge weightsg=[[INFfor_inrange(N)]for_inrange(N)]# Initialize shortest path matrix dis, initially same as gdis=[[INFfor_inrange(N)]for_inrange(N)]# Initialize pos matrix to record intermediate nodes on shortest pathspos=[[-1for_inrange(N)]for_inrange(N)]# Set diagonal to 0 (distance from a node to itself)foriinrange(N):g[i][i]=0dis[i][i]=0# Build adjacency matrix from input edges (undirected graph)foru,v,winedges:# Convert 1-based indices to 0-basedu-=1v-=1# For undirected graph, edges are bidirectionalg[u][v]=min(g[u][v],w)g[v][u]=min(g[v][u],w)dis[u][v]=min(dis[u][v],w)dis[v][u]=min(dis[v][u],w)# Initialize minimum cycle length to infinitymin_cycle_len=INF# Initialize minimum cycle pathmin_cycle_path=[]# Core part of Floyd-Warshall algorithm# k as intermediate node (0-based index)forkinrange(N):# Before updating dis[i][j], check if a smaller cycle can be formed via node k# The cycle consists of i -> k -> j -> ... -> i# Here dis[i][j] is the shortest path considering nodes 0 to k-1 as intermediate nodes# The C++ code uses loops with i < k and j < i; we follow the same logic here (0-based)foriinrange(k):# 0 <= i < kforjinrange(i):# 0 <= j < i# Check if i, k, j form a cycle connected via dis[i][j]# Ensure original edges g[i][k] and g[k][j] exist (not INF)# and the shortest path dis[i][j] from i to j exists (not INF)ifg[i][k]!=INFandg[k][j]!=INFanddis[i][j]!=INF:current_cycle_len=g[i][k]+g[k][j]+dis[i][j]ifcurrent_cycle_len<min_cycle_len:min_cycle_len=current_cycle_len# Reconstruct the pathpath=[0]*(N+5)# Temporary array to store the path, large enoughcnt=0# Add i, k, j in orderpath[cnt]=icnt+=1path[cnt]=kcnt+=1path[cnt]=jcnt+=1# Get intermediate nodes on the shortest path from j to i (using previously computed dis and pos)cnt=get_path(j,i,pos,path,cnt)# Extract actual path nodes (remove unused parts)# Convert 0-based indices to 1-basedmin_cycle_path=[node+1fornodeinpath[:cnt]]# Standard Floyd-Warshall update for shortest pathsforiinrange(N):forjinrange(N):if(dis[i][k]!=INFanddis[k][j]!=INFanddis[i][j]>dis[i][k]+dis[k][j]):dis[i][j]=dis[i][k]+dis[k][j]# Record that the shortest path from i to j passes through kpos[i][j]=kreturnmin_cycle_len,min_cycle_path
Given an undirected graph with \(n\) points, find a cycle containing at least \(3\) points, where the nodes on the cycle are distinct, and the sum of the lengths of the edges on the cycle is minimized.
This problem is known as the minimum cycle problem in undirected graphs.
You need to output the scheme of the minimum cycle. If the minimum cycle is not unique, output any one.
\(n \le 100\)
The time complexity of \(O(n^3)\) is acceptable. Apply the Floyd algorithm for finding the minimum cycle.
#include<bits/stdc++.h>usinglint=longlong;// Define a sufficiently large constant for the maximum number of nodes in the graphconstintMAXN=110;// Define a sufficiently large value to represent infinity, initialize minimum cycle lengthlintans=1e9;// lint is an alias for long long// Number of nodes n, number of edges m// cnt records the number of nodes in the minimum cycle path// path stores the nodes of the minimum cycle pathintn,m,cnt,path[MAXN];// g stores the original graph's adjacency matrix// dis stores the shortest path matrix (updated during Floyd-Warshall algorithm)// pos records intermediate nodes on shortest paths, pos[i][j] = k means the shortest path from i to j passes through kintg[MAXN][MAXN],dis[MAXN][MAXN],pos[MAXN][MAXN];// Recursive function: get intermediate nodes on the shortest path from node u to node v// Reconstruct the path based on the pos matrixvoidget_path(intu,intv){// If pos[u][v] is 0, there is no intermediate node between u and v, return directlyif(pos[u][v]==0)return;// Get intermediate node kintk=pos[u][v];// Recursively get the path from u to kget_path(u,k);// Add intermediate node k to the pathpath[++cnt]=k;// Recursively get the path from k to vget_path(k,v);}// Floyd-Warshall algorithm function: find the minimum cycle in the graphvoidFloyd(){// Outer loop: k as intermediate node (1 to n)for(intk=1;k<=n;++k){// Inner loops: i and j, used to check if a smaller cycle can be formed via node k// Loop order: i from 1 to k-1, j from 1 to i-1// This checks cycles of the form i -> k -> j -> ... -> ifor(inti=1;i<k;++i)for(intj=1;j<i;++j)// Check if connecting i and j via node k forms a smaller cycle// Cycle length is original edge weight g[i][k] + original edge weight g[k][j]// + current shortest path dis[i][j]if(ans>(longlong)g[i][k]+g[k][j]+dis[i][j]){// Found a smaller cycleans=g[i][k]+g[k][j]+dis[i][j];// Update minimum cycle lengthcnt=0;// Reset path count// Add i, k, j to the path in orderpath[++cnt]=i,path[++cnt]=k,path[++cnt]=j;// Get intermediate nodes on the shortest path from j to i, add to pathget_path(j,i);}// Standard Floyd-Warshall update for shortest paths// i from 1 to n, j from 1 to nfor(inti=1;i<=n;++i)for(intj=1;j<=n;++j){// If a shorter path from i to j can be obtained via intermediate node kif(dis[i][j]>dis[i][k]+dis[k][j]){// Update shortest pathdis[i][j]=dis[i][k]+dis[k][j];// Record that the shortest path from i to j passes through kpos[i][j]=k;}}}}// Main functionintmain(){// Read number of nodes n and number of edges mstd::cin>>n>>m;// Initialize original adjacency matrix g, set all edge weights to infinity (0x3f usually represents a large value)memset(g,0x3f,sizeof(g));// Set distance from a node to itself to 0for(inti=1;i<=n;++i)g[i][i]=0;// Read m edges, build original adjacency matrix g// For undirected graph, edges are bidirectional, take the smaller weightfor(inti=0,u,v,w;i<m;++i){std::cin>>u>>v>>w;g[u][v]=g[v][u]=std::min(g[u][v],w);}// Copy original adjacency matrix g to shortest path matrix dismemcpy(dis,g,sizeof(g));// Call Floyd algorithm to find minimum cycleFloyd();// Determine if a cycle exists based on minimum cycle lengthif(ans==1e9){// If minimum cycle length is still infinity, no cycle existsputs("No solution.");}else{// If a cycle exists, print the path nodes// std::cout << "ans = " << ans << std::endl; // Print minimum cycle length (commented out)// Print path nodes, separated by spacesfor(inti=1;i<=cnt;++i)std::cout<<path[i]<<(i==cnt?"":" ");// No space after the last nodestd::cout<<std::endl;// Newline after printing the path}return0;}
importcopyimportsys# Define a sufficiently large value to represent infinityINF=sys.maxsizedefget_path(i,j,pos,path,cnt):""" Recursively get the intermediate nodes on the shortest path from node i to node j. Args: i (int): Starting node (0-based index). j (int): Ending node (0-based index). pos (list[list[int]]): Matrix recording intermediate nodes on shortest paths. pos[i][j] = k means the shortest path from i to j passes through k. path (list[int]): List storing path nodes (using 0-based index). cnt (int): Current number of path nodes. Returns: int: Updated number of path nodes. """# If pos[i][j] is -1, there is no intermediate node between i and jifpos[i][j]==-1:returncnt# Get intermediate node kk=pos[i][j]# Recursively get the path from i to kcnt=get_path(i,k,pos,path,cnt)# Add intermediate node k to the pathpath[cnt]=kcnt+=1# Recursively get the path from k to jcnt=get_path(k,j,pos,path,cnt)returncntdeffind_minimum_cycle_undirected(n,edges):""" Find the minimum cycle in an undirected graph using the Floyd-Warshall algorithm. Args: n (int): Number of nodes in the graph (1 to n). edges (list[tuple]): List of edges, each element is (u, v, w), indicating an edge between nodes u and v with weight w. Node indices are 1 to n. Returns: tuple: Contains the length of the minimum cycle and the path. If no cycle exists, return (INF, []). The path is a list of node indices (1-based index). """# Use 0-based indexing internallyN=n# Initialize adjacency matrix g, representing original edge weightsg=[[INFfor_inrange(N)]for_inrange(N)]# Initialize shortest path matrix dis, initially same as gdis=[[INFfor_inrange(N)]for_inrange(N)]# Initialize pos matrix to record intermediate nodes on shortest pathspos=[[-1for_inrange(N)]for_inrange(N)]# Set diagonal to 0 (distance from a node to itself)foriinrange(N):g[i][i]=0dis[i][i]=0# Build adjacency matrix from input edges (undirected graph)foru,v,winedges:# Convert 1-based indices to 0-basedu-=1v-=1# For undirected graph, edges are bidirectionalg[u][v]=min(g[u][v],w)g[v][u]=min(g[v][u],w)dis[u][v]=min(dis[u][v],w)dis[v][u]=min(dis[v][u],w)# Initialize minimum cycle length to infinitymin_cycle_len=INF# Initialize minimum cycle pathmin_cycle_path=[]# Core part of Floyd-Warshall algorithm# k as intermediate node (0-based index)forkinrange(N):# Before updating dis[i][j], check if a smaller cycle can be formed via node k# The cycle consists of i -> k -> j -> ... -> i# Here dis[i][j] is the shortest path considering nodes 0 to k-1 as intermediate nodes# The C++ code uses loops with i < k and j < i; we follow the same logic here (0-based)foriinrange(k):# 0 <= i < kforjinrange(i):# 0 <= j < i# Check if i, k, j form a cycle connected via dis[i][j]# Ensure original edges g[i][k] and g[k][j] exist (not INF)# and the shortest path dis[i][j] from i to j exists (not INF)ifg[i][k]!=INFandg[k][j]!=INFanddis[i][j]!=INF:current_cycle_len=g[i][k]+g[k][j]+dis[i][j]ifcurrent_cycle_len<min_cycle_len:min_cycle_len=current_cycle_len# Reconstruct the pathpath=[0]*(N+5)# Temporary array to store the path, large enoughcnt=0# Add i, k, j in orderpath[cnt]=icnt+=1path[cnt]=kcnt+=1path[cnt]=jcnt+=1# Get intermediate nodes on the shortest path from j to i (using previously computed dis and pos)cnt=get_path(j,i,pos,path,cnt)# Extract actual path nodes (remove unused parts)# Convert 0-based indices to 1-basedmin_cycle_path=[node+1fornodeinpath[:cnt]]# Standard Floyd-Warshall update for shortest pathsforiinrange(N):forjinrange(N):if(dis[i][k]!=INFanddis[k][j]!=INFanddis[i][j]>dis[i][k]+dis[k][j]):dis[i][j]=dis[i][k]+dis[k][j]# Record that the shortest path from i to j passes through kpos[i][j]=kreturnmin_cycle_len,min_cycle_path# --- Main program entry ---if__name__=="__main__":# Read number of nodes n and number of edges mn,m=map(int,sys.stdin.readline().split())# Read edge informationedges=[]for_inrange(m):u,v,w=map(int,sys.stdin.readline().split())edges.append((u,v,w))# Find minimum cyclemin_len,path=find_minimum_cycle_undirected(n,edges)# Output resultifmin_len==INF:print("No solution.")else:# Print path nodes (1-based index), separated by spacesprint(" ".join(map(str,path)))
Example 2
GDOI2018 Day2 Patrol
Given an undirected graph with \(n\) points and no negative-weight edges, perform \(q\) operations of three types:
Delete a point in the graph and its associated edges.
Restore a deleted point and its associated edges.
Query the size of the minimum cycle containing point \(x\).
For \(50\%\) of the data, \(n,q \le 100\).
For any simple cycle containing point \(x\), there exist two edges adjacent to \(x\). Deleting any one of them turns the simple cycle into a simple path.
Thus, enumerate all edges adjacent to \(x\), delete one each time, and run Dijkstra once.
Alternatively, run Floyd to find the minimum cycle for each query directly, \(O(qn^3)\).
For \(100\%\) of the data, \(n,q \le 400\).
Still use the Floyd algorithm for finding the minimum cycle.
If there is no deletion, removing the query point splits the simple cycle into a simple path.
However, the second step of solving is replaced by Floyd.
Thus, the answer requires the distance between any two points without passing through the query point \(x\).
How to handle online?
Force offline, using an offline method to avoid deletion operations.
Sort the queries in chronological order and build a segment tree over these queries.
The appearance time of each point covers all queries except the moments when the point is queried. Suppose a point is queried \(x\) times, then its appearance time can be considered as \(x + 1\) intervals, inserted into the segment tree.
After completion, traverse the entire segment tree. When entering a point, store a backup of the Floyd array, then add all points inserted in this interval. When leaving, restore using the backup array.
The time complexity of this approach is \(O(qn^2\log q)\).
There is also a more efficient online approach.
For a query on point \(x\), run a single-source shortest path from \(x\), then build the shortest path tree, and determine which subtree each point belongs to relative to \(x\).
Then, there must exist a non-tree edge whose two endpoints are in different subtrees of the root, such that this non-tree edge \(+\) the paths from the two endpoints to the root forms the minimum cycle.
Proof:
Clearly, the minimum cycle contains at least one non-tree edge with endpoints in different subtrees of the root.
Assume this edge is \((u,v)\). Then the path from \(x\) to \(u\) in the shortest path tree is the shortest among all paths from \(x\) to \(u\), and similarly for \(x\) to \(v\). Thus, the cycle \(x\to u\to v\to x\) cannot be longer than the minimum cycle.
Therefore, enumerate all non-tree edges and update the answer.
The complexity per query is that of running a single-source shortest path, \(O(n^2)\).