Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,9 @@ jobs:
- name: Release build
run: cargo build --release --examples
- name: oj test
run: cargo run --release --bin oj_test
run: |
ulimit -s unlimited
cargo run --release --bin oj_test

publish-doc:
runs-on: ubuntu-latest
Expand Down
49 changes: 49 additions & 0 deletions libs/detect_cycle/examples/cycle_detection_undirected.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
// problem: https://judge.yosupo.jp/problem/cycle_detection_undirected
// judge_program_rs: ./judge_cycle_detection_undirected.rs

use detect_cycle::detect_cycle_undirected;
use proconio::input;

fn main() {
input! {
n: usize,
m: usize,
edges: [(usize, usize); m],
}
let Some(cycle) = detect_cycle_undirected(n, &edges) else {
println!("-1");
return;
};

let (u, v) = edges[cycle[0]];
let mut current = if cycle.len() == 1 {
u
} else {
let (next_u, next_v) = edges[cycle[1]];
if v == next_u || v == next_v { u } else { v }
};
let mut vertices = Vec::with_capacity(cycle.len());
for &edge_id in &cycle {
vertices.push(current);
let (u, v) = edges[edge_id];
current = if current == u { v } else { u };
}

println!("{}", cycle.len());
println!(
"{}",
vertices
.iter()
.map(|v| v.to_string())
.collect::<Vec<_>>()
.join(" ")
);
println!(
"{}",
cycle
.iter()
.map(|e| e.to_string())
.collect::<Vec<_>>()
.join(" ")
);
}
102 changes: 102 additions & 0 deletions libs/detect_cycle/examples/judge_cycle_detection_undirected.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
use proconio::{
input,
source::{Source, once::OnceSource},
};

use std::env;
use std::fs::File;
use std::io;
use std::io::{BufRead, BufReader};

struct Input {
n: usize,
m: usize,
edges: Vec<(usize, usize)>,
}

enum Output {
NotFound,
Found {
vertices: Vec<usize>,
edge_ids: Vec<usize>,
},
}

fn read_input(input: impl BufRead) -> Input {
let mut source = OnceSource::new(input);
input! {
from &mut source,
n: usize,
m: usize,
edges: [(usize, usize); m],
}
assert!(source.is_empty());
Input { n, m, edges }
}

fn read_output(output: impl BufRead) -> Output {
let mut source = OnceSource::new(output);
input! {
from &mut source,
len: isize,
}
if len < 0 {
assert_eq!(len, -1);
assert!(source.is_empty());
return Output::NotFound;
}

let len = len as usize;
assert!(len >= 1);
input! {
from &mut source,
vertices: [usize; len],
edge_ids: [usize; len],
}
assert!(source.is_empty());
Output::Found { vertices, edge_ids }
}

fn validate_cycle(input: &Input, vertices: &[usize], edge_ids: &[usize]) {
let len = vertices.len();
assert_eq!(edge_ids.len(), len);

let mut seen_vertices = vec![false; input.n];
let mut seen_edges = vec![false; input.m];
for i in 0..len {
let vertex = vertices[i];
let next_vertex = vertices[(i + 1) % len];
let edge_id = edge_ids[i];

assert!(vertex < input.n);
assert!(!seen_vertices[vertex]);
seen_vertices[vertex] = true;

assert!(edge_id < input.m);
assert!(!seen_edges[edge_id]);
seen_edges[edge_id] = true;

let (u, v) = input.edges[edge_id];
assert!((u == vertex && v == next_vertex) || (u == next_vertex && v == vertex));
}
}

fn main() -> io::Result<()> {
let args: Vec<String> = env::args().collect();
assert_eq!(args.len(), 4);
let (input, my_output, expected_output) = (&args[1], &args[2], &args[3]);

let input = read_input(BufReader::new(File::open(input)?));
let my_output = read_output(BufReader::new(File::open(my_output)?));
let expected_output = read_output(BufReader::new(File::open(expected_output)?));

match (my_output, expected_output) {
(Output::NotFound, Output::NotFound) => {}
(Output::Found { vertices, edge_ids }, Output::Found { .. }) => {
validate_cycle(&input, &vertices, &edge_ids)
}
_ => panic!("cycle existence does not match the expected output"),
}

Ok(())
}
87 changes: 54 additions & 33 deletions libs/detect_cycle/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,23 +32,26 @@
pub fn detect_cycle_undirected(n: usize, edges: &[(usize, usize)]) -> Option<Vec<usize>> {
fn dfs(
curr: usize,
prev: usize,
prev_edge: Option<usize>,
g: &[Vec<(usize, usize)>],
seen: &mut Vec<bool>,
parent: &mut Vec<(usize, usize)>,
) -> Option<(usize, usize)> {
seen[curr] = true;
depth: &mut [Option<usize>],
path: &mut Vec<usize>,
) -> Option<Vec<usize>> {
depth[curr] = Some(path.len());
for &(nxt, idx) in &g[curr] {
if nxt == prev {
if Some(idx) == prev_edge {
continue;
}
if seen[nxt] {
return Some((nxt, curr));
if let Some(nxt_depth) = depth[nxt] {
let mut cycle = path[nxt_depth..].to_vec();
cycle.push(idx);
return Some(cycle);
}
parent[nxt] = (curr, idx);
if let Some((start, end)) = dfs(nxt, curr, g, seen, parent) {
return Some((start, end));
path.push(idx);
if let Some(cycle) = dfs(nxt, Some(idx), g, depth, path) {
return Some(cycle);
}
path.pop();
}
None
}
Expand All @@ -58,31 +61,15 @@ pub fn detect_cycle_undirected(n: usize, edges: &[(usize, usize)]) -> Option<Vec
g[u].push((v, idx));
g[v].push((u, idx));
}
let mut seen = vec![false; n];
let mut parent = vec![(!0, !0); n];
let mut depth = vec![None; n];
let mut path = Vec::new();

for v in 0..n {
if seen[v] {
if depth[v].is_some() {
continue;
}
if let Some((cycle_start, cycle_end)) = dfs(v, !0, &g, &mut seen, &mut parent) {
let mut cycle = Vec::new();
{
let mut curr = cycle_end;
while curr != cycle_start {
let (par, idx) = parent[curr];
cycle.push(idx);
curr = par;
}
}
// cycle_end <- parent[cycle_end] <- parent[parent[cycle_end]] <- ... <- cycle_start
for (idx, &(u, v)) in edges.iter().enumerate() {
if (u, v) == (cycle_start, cycle_end) || (u, v) == (cycle_end, cycle_start) {
cycle.push(idx);
return Some(cycle);
}
}
unreachable!();
if let Some(cycle) = dfs(v, None, &g, &mut depth, &mut path) {
return Some(cycle);
}
}
None
Expand Down Expand Up @@ -165,7 +152,41 @@ pub fn detect_cycle_directed(n: usize, edges: &[(usize, usize)]) -> Option<Vec<u

#[cfg(test)]
mod tests {
use crate::detect_cycle_directed;
use crate::{detect_cycle_directed, detect_cycle_undirected};

#[test]
fn test_undirected_triangle() {
let cycle = detect_cycle_undirected(3, &[(0, 2), (2, 1), (1, 0)]).unwrap();
assert!(
[
vec![0, 1, 2],
vec![1, 2, 0],
vec![2, 0, 1],
vec![0, 2, 1],
vec![2, 1, 0],
vec![1, 0, 2],
]
.contains(&cycle)
);
}

#[test]
fn test_undirected_v() {
let cycle = detect_cycle_undirected(3, &[(0, 2), (0, 1)]);
assert_eq!(cycle, None);
}

#[test]
fn test_undirected_parallel_edges() {
let cycle = detect_cycle_undirected(2, &[(0, 1), (0, 1)]).unwrap();
assert!([vec![0, 1], vec![1, 0]].contains(&cycle));
}

#[test]
fn test_undirected_self_loop() {
let cycle = detect_cycle_undirected(1, &[(0, 0)]);
assert_eq!(cycle, Some(vec![0]));
}

#[test]
fn test_directed_triangle() {
Expand Down
Loading