From 34d8f3ebcbb6f0160c5a2c001afba73280426368 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 28 Jul 2025 13:01:01 +0000 Subject: [PATCH 1/3] Initial plan From 15a22eed42f9341574d6f4e28d70721e6fd9c6db Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 28 Jul 2025 13:13:06 +0000 Subject: [PATCH 2/3] Add comprehensive documentation for strongly_connected_components, auxiliary_tree, ext_gcd, and factorials Co-authored-by: ia7ck <23146842+ia7ck@users.noreply.github.com> --- algo/auxiliary_tree/src/lib.rs | 133 ++++++++++-- algo/ext_gcd/src/lib.rs | 122 ++++++++++- algo/factorials/src/lib.rs | 192 +++++++++++++++++- algo/strongly_connected_components/src/lib.rs | 119 ++++++++++- 4 files changed, 541 insertions(+), 25 deletions(-) diff --git a/algo/auxiliary_tree/src/lib.rs b/algo/auxiliary_tree/src/lib.rs index 1aaa1d86..3fc8a7d4 100644 --- a/algo/auxiliary_tree/src/lib.rs +++ b/algo/auxiliary_tree/src/lib.rs @@ -1,28 +1,135 @@ +//! Auxiliary Tree(補助木・仮想木)を構築するライブラリです。 +//! +//! Auxiliary Tree は、元の木から指定された頂点集合とそれらの最小共通祖先(LCA)のみを +//! 含む部分木を効率的に構築する手法です。主に木上のクエリ処理の高速化に使用されます。 +//! +//! # 計算量 +//! +//! グラフの頂点数を n、指定された頂点集合のサイズを k として: +//! - 時間計算量: O(k log n + k log k) +//! - 空間計算量: O(k) +//! +//! ※ HashMap のコストは無視しています +//! +//! # 用途 +//! +//! - 木上での部分集合に対するクエリの高速化 +//! - 競技プログラミングでの木構造問題の最適化 +//! - 大きな木の中で特定の頂点群に関する計算を効率化 +//! +//! # 前提条件 +//! +//! - 元のグラフが木構造である +//! - LCA(最小共通祖先)が効率的に計算できる +//! - 各頂点に対して pre-order での訪問順序が与えられている +//! +//! # Examples +//! +//! ## 基本的な使用例 +//! +//! ``` +//! use auxiliary_tree::auxiliary_tree; +//! use lowest_common_ancestor::LowestCommonAncestor; +//! use std::collections::HashMap; +//! +//! // 線形の木: 0 -- 1 -- 2 -- 3 -- 4 +//! let lca = LowestCommonAncestor::new(5, 0, &[(0, 1), (1, 2), (2, 3), (3, 4)]); +//! let inv_ord = vec![0, 1, 2, 3, 4]; // pre-order での順序 +//! +//! // 頂点 {1, 3, 4} に対する Auxiliary Tree を構築 +//! let (root, tree) = auxiliary_tree(&[1, 3, 4], &inv_ord, &lca); +//! +//! // ルートは 1(最も早く訪問される頂点) +//! assert_eq!(root, 1); +//! +//! // 構築された木の構造を確認 +//! assert!(tree.contains_key(&1)); +//! assert!(tree.contains_key(&3)); +//! assert!(tree.contains_key(&4)); +//! ``` + use std::collections::HashMap; use lowest_common_ancestor::LowestCommonAncestor; -/// [auxiliary tree](https://noshi91.github.io/algorithm-encyclopedia/auxiliary-tree) です。 +/// 指定された頂点集合に対する Auxiliary Tree を構築します。 /// -/// # 計算量 +/// [Auxiliary Tree](https://noshi91.github.io/algorithm-encyclopedia/auxiliary-tree) は、 +/// 元の木から指定された頂点集合とそれらの LCA のみを含む最小の部分木です。 +/// +/// アルゴリズムの詳細は [参考記事](https://smijake3.hatenablog.com/entry/2019/09/15/200200) を参照してください。 /// -/// hash map のコストは無視する。 +/// # 引数 /// -/// グラフの頂点数を n, `nodes.len()` を k として、O(klogn + klogk) +/// * `nodes`: 対象とする頂点の集合。{0, 1, ..., n-1} の部分集合である必要があります +/// * `inv_ord`: pre-order(行きがけ順)での各頂点の訪問順序 +/// - 頂点 `i` は pre-order で `inv_ord[i]` 番目に訪問されます +/// * `lca`: 2頂点間の LCA を計算する構造体。`.get(u, v)` メソッドを持つ必要があります /// -/// # 引数 +/// # 戻り値 +/// +/// `(root, graph)` のタプルを返します: +/// - `root`: 構築された Auxiliary Tree のルート頂点 +/// - `graph`: HashMap で表現された木構造 +/// - `graph.contains_key(&i)`: 頂点 `i` が Auxiliary Tree に含まれる +/// - `graph[&i]`: 頂点 `i` の子頂点のリスト +/// - `!graph.contains_key(&i)`: 頂点 `i` は Auxiliary Tree に含まれない +/// +/// # Panics +/// +/// `nodes` が空の場合にパニックします。 +/// +/// # Examples +/// +/// ``` +/// use auxiliary_tree::auxiliary_tree; +/// use lowest_common_ancestor::LowestCommonAncestor; +/// +/// // 線形の木: 0 -- 1 -- 2 -- 3 -- 4 +/// let lca = LowestCommonAncestor::new(5, 0, &[(0, 1), (1, 2), (2, 3), (3, 4)]); +/// let inv_ord = vec![0, 1, 2, 3, 4]; +/// +/// // 単一頂点の場合 +/// let (root, tree) = auxiliary_tree(&[2], &inv_ord, &lca); +/// assert_eq!(root, 2); +/// assert_eq!(tree.get(&2), Some(&vec![])); // 子はなし +/// ``` /// -/// * `nodes`: {0, 1, ..., n - 1} の部分集合 -/// * `inv_ord`: pre-order (行きがけ順) の列の添字と値を反対にしたもの -/// * 頂点 `i` は行きがけ順で `inv_ord[i]` 番目に現われる -/// * `lca`: 2頂点の LCA を得る `.get(u, v)` を実装した構造体 +/// # 競技プログラミングでの応用例 /// -/// # 返り値 +/// ``` +/// use auxiliary_tree::auxiliary_tree; +/// use lowest_common_ancestor::LowestCommonAncestor; +/// use std::collections::HashMap; /// -/// `(root, g)` +/// // 木上のクエリ問題での使用例 +/// // 例:指定された頂点群を含む最小の部分木のサイズを求める +/// fn solve_tree_query( +/// n: usize, +/// edges: &[(usize, usize)], +/// query_nodes: &[usize] +/// ) -> usize { +/// if query_nodes.is_empty() { +/// return 0; +/// } +/// +/// let lca = LowestCommonAncestor::new(n, 0, edges); +/// +/// // DFS で pre-order を計算(簡略化) +/// let inv_ord: Vec = (0..n).collect(); +/// +/// let (_, aux_tree) = auxiliary_tree(query_nodes, &inv_ord, &lca); +/// +/// // Auxiliary Tree のサイズが答え +/// aux_tree.len() +/// } /// -/// * ` g.contains_key(&i)`: 頂点 `i` が圧縮後の木に含まれて、子のリストが `g[&i]` である -/// * `!g.contains_key(&i)`: 頂点 `i` が圧縮後の木に含まれない +/// // テスト +/// let edges = vec![(0, 1), (1, 2), (1, 3), (3, 4)]; +/// let query = vec![2, 4]; +/// let result = solve_tree_query(5, &edges, &query); +/// assert!(result >= 2); // 少なくとも指定された頂点は含まれる +/// ``` pub fn auxiliary_tree( nodes: &[usize], inv_ord: &[usize], diff --git a/algo/ext_gcd/src/lib.rs b/algo/ext_gcd/src/lib.rs index 5a6aca3d..9d0d48b8 100644 --- a/algo/ext_gcd/src/lib.rs +++ b/algo/ext_gcd/src/lib.rs @@ -1,4 +1,80 @@ -/// g = gcd(a, b), ax + by = g を満たす (x, y, g) を返します。 +//! 拡張ユークリッド互除法(Extended Euclidean Algorithm)のライブラリです。 +//! +//! 2つの整数 a, b に対して、ベズー恒等式 ax + by = gcd(a, b) を満たす +//! 整数解 (x, y) と最大公約数 gcd(a, b) を同時に求めます。 +//! +//! # 計算量 +//! +//! - 時間計算量: O(log(min(|a|, |b|))) +//! - 空間計算量: O(log(min(|a|, |b|))) (再帰呼び出しによる) +//! +//! # 用途 +//! +//! - 乗法逆元の計算(mod p での a^(-1) の計算) +//! - 一次不定方程式 ax + by = c の解の計算 +//! - 中国剰余定理の実装 +//! - 競技プログラミングでの整数問題の解決 +//! +//! # Examples +//! +//! ## 基本的な使用例 +//! +//! ``` +//! use ext_gcd::ext_gcd; +//! +//! // 48x + 30y = gcd(48, 30) = 6 の解 +//! let (x, y, g) = ext_gcd(48, 30); +//! assert_eq!(g, 6); +//! assert_eq!(48 * x + 30 * y, g); +//! ``` +//! +//! ## 乗法逆元の計算 +//! +//! ``` +//! use ext_gcd::ext_gcd; +//! +//! // mod 1000000007 での 123 の逆元を求める +//! let a = 123; +//! let p = 1000000007; // 素数 +//! let (x, _y, g) = ext_gcd(a, p); +//! +//! assert_eq!(g, 1); // gcd(a, p) = 1 なので逆元が存在 +//! let inv = x.rem_euclid(p); +//! assert_eq!((a * inv) % p, 1); // a * inv ≡ 1 (mod p) +//! ``` +//! +//! ## 一次不定方程式の解 +//! +//! ``` +//! use ext_gcd::ext_gcd; +//! +//! // 方程式 15x + 25y = 5 の解を求める +//! let (a, b, c) = (15, 25, 5); +//! let (x0, y0, g) = ext_gcd(a, b); +//! +//! assert_eq!(g, 5); // gcd(15, 25) = 5 +//! assert!(c % g == 0); // c が g の倍数なので解が存在 +//! +//! // 特解を計算 +//! let k = c / g; +//! let (x, y) = (x0 * k, y0 * k); +//! assert_eq!(a * x + b * y, c); +//! ``` + +/// 拡張ユークリッド互除法を実行します。 +/// +/// ベズー恒等式 ax + by = gcd(a, b) を満たす整数解 (x, y) と +/// 最大公約数 gcd(a, b) を計算します。 +/// +/// # 引数 +/// +/// - `a`, `b`: 任意の整数(負数も可能) +/// +/// # 戻り値 +/// +/// `(x, y, g)` のタプルを返します: +/// - `x`, `y`: ベズー恒等式 ax + by = g を満たす整数 +/// - `g`: a と b の最大公約数(常に非負) /// /// # Examples /// ``` @@ -11,6 +87,50 @@ /// assert_eq!(ext_gcd(42, 0), (1, 0, 42)); /// assert_eq!(ext_gcd(0, 0), (0, 0, 0)); /// ``` +/// +/// # 競技プログラミングでの応用例 +/// +/// ## ModInt での逆元計算 +/// ``` +/// use ext_gcd::ext_gcd; +/// +/// fn mod_inverse(a: i64, m: i64) -> Option { +/// let (x, _y, g) = ext_gcd(a, m); +/// if g == 1 { +/// Some(x.rem_euclid(m)) +/// } else { +/// None // 逆元が存在しない +/// } +/// } +/// +/// let p = 1000000007; +/// let a = 123456; +/// if let Some(inv) = mod_inverse(a, p) { +/// assert_eq!((a * inv) % p, 1); +/// } +/// ``` +/// +/// ## 中国剰余定理での使用 +/// ``` +/// use ext_gcd::ext_gcd; +/// +/// fn chinese_remainder_theorem(r1: i64, m1: i64, r2: i64, m2: i64) -> Option<(i64, i64)> { +/// let (x, _y, g) = ext_gcd(m1, m2); +/// if (r2 - r1) % g != 0 { +/// return None; // 解が存在しない +/// } +/// +/// let lcm = m1 / g * m2; +/// let result = (r1 + m1 * (r2 - r1) / g * x).rem_euclid(lcm); +/// Some((result, lcm)) +/// } +/// +/// // x ≡ 2 (mod 3) かつ x ≡ 3 (mod 5) の解 +/// if let Some((x, _m)) = chinese_remainder_theorem(2, 3, 3, 5) { +/// assert_eq!(x % 3, 2); +/// assert_eq!(x % 5, 3); +/// } +/// ``` #[allow(clippy::many_single_char_names)] pub fn ext_gcd(a: i64, b: i64) -> (i64, i64, i64) { if b == 0 { diff --git a/algo/factorials/src/lib.rs b/algo/factorials/src/lib.rs index d735ec2c..06fbf070 100644 --- a/algo/factorials/src/lib.rs +++ b/algo/factorials/src/lib.rs @@ -1,3 +1,65 @@ +//! 階乗とその乗法逆元、二項係数を効率的に計算するライブラリです。 +//! +//! 素数を法とする環での階乗の前計算と、それを利用した二項係数の高速計算を提供します。 +//! 多くの組み合わせ論的計算において、二項係数を O(1) で計算できるため非常に有用です。 +//! +//! # 計算量 +//! +//! - 前計算: O(n) +//! - 各クエリ(factorial, binomial など): O(1) +//! +//! # 用途 +//! +//! - 二項係数 C(n, k) の高速計算 +//! - 組み合わせ論的問題の解決 +//! - 動的プログラミングでの状態数計算 +//! - 確率計算(mod での除算を含む) +//! - 競技プログラミングでの数学問題 +//! +//! # 制約 +//! +//! - 法 `modulo` は素数である必要があります +//! - `modulo >= size` である必要があります(階乗の計算範囲内) +//! +//! # Examples +//! +//! ## 基本的な使用例 +//! +//! ``` +//! use factorials::Factorial; +//! +//! let modulo = 1_000_000_007; +//! let f = Factorial::new(100, modulo); +//! +//! // 階乗の計算 +//! assert_eq!(f.factorial(5), 120); // 5! = 120 +//! +//! // 二項係数の計算 +//! assert_eq!(f.binomial(5, 2), 10); // C(5,2) = 10 +//! assert_eq!(f.binomial(10, 3), 120); // C(10,3) = 120 +//! ``` +//! +//! ## 組み合わせ論的問題での使用例 +//! +//! ``` +//! use factorials::Factorial; +//! +//! let modulo = 1_000_000_007; +//! let f = Factorial::new(1000, modulo); +//! +//! // n 個の異なる玉から k 個選ぶ組み合わせ +//! let n = 50; +//! let k = 20; +//! let combinations = f.binomial(n, k); +//! +//! // 重複組み合わせ H(n, k) = C(n+k-1, k) +//! let repeated_combinations = f.binomial(n + k - 1, k); +//! +//! // カタラン数 C_n = C(2n, n) / (n+1) +//! let catalan_5 = f.binomial(10, 5) * f.inversion(6) % modulo; +//! assert_eq!(catalan_5, 42); // 5番目のカタラン数 +//! ``` + /// 階乗とその乗法逆元、そして二項係数を扱います。 pub struct Factorial { factorial: Vec, @@ -6,27 +68,50 @@ pub struct Factorial { } impl Factorial { - /// `1` 以上 `size` 未満の `n` について、`n` の階乗 (mod `modulo`) と、その乗法逆元を O(`size`) 時間で計算します。[参考](https://drken1215.hatenablog.com/entry/2018/06/08/210000) + /// 指定されたサイズと法で階乗計算構造体を初期化します。 + /// + /// `0` 以上 `size` 未満の `n` について、`n` の階乗 (mod `modulo`) と、 + /// その乗法逆元を O(`size`) 時間で前計算します。 + /// [参考](https://drken1215.hatenablog.com/entry/2018/06/08/210000) /// - /// 逆元を正しく計算するためには + /// # 前提条件 /// + /// 逆元を正しく計算するためには以下の条件が必要です: /// - `modulo` が素数 /// - `modulo >= size` /// - /// である必要があります。 - /// /// # Examples /// /// ``` /// use factorials::Factorial; /// - /// let modulo = 1_000_000_000 + 7; + /// let modulo = 1_000_000_007; /// let f = Factorial::new(100, modulo); /// for i in 1..100 { /// assert_eq!(f.factorial(i) * f.inversion(i) % modulo, 1); /// } /// ``` /// + /// ## 競技プログラミングでの典型的な使用パターン + /// + /// ``` + /// use factorials::Factorial; + /// + /// // 最大 10^6 までの二項係数を扱う場合 + /// let modulo = 1_000_000_007; + /// let max_n = 1_000_000; + /// let f = Factorial::new(max_n + 1, modulo); + /// + /// // 多数のクエリを O(1) で処理 + /// let mut total = 0; + /// for n in 1..=100 { + /// for k in 0..=n { + /// total = (total + f.binomial(n, k)) % modulo; + /// } + /// } + /// // total は 2^1 + 2^2 + ... + 2^100 (mod p) + /// ``` + /// /// # Panics /// /// `modulo` が `size` より小さい場合パニックです。 @@ -61,14 +146,19 @@ impl Factorial { } } - /// `modulo` が素数でない場合パニックです。素数判定に O(sqrt(`modulo`)) 時間かかります。 + /// 素数性をチェックしてから階乗計算構造体を初期化します。 + /// + /// `new` メソッドと同じ機能ですが、`modulo` が素数でない場合にパニックします。 + /// 素数判定に O(√`modulo`) 時間かかります。 /// /// # Panics /// + /// `modulo` が素数でない場合、または `modulo < size` の場合にパニックします。 + /// /// ```should_panic /// use factorials::Factorial; /// - /// let modulo = 42; + /// let modulo = 42; // 素数ではない /// Factorial::new_checking_modulo_prime(10, 42); /// ``` pub fn new_checking_modulo_prime(size: usize, modulo: u64) -> Self { @@ -78,17 +168,51 @@ impl Factorial { Self::new(size, modulo) } + /// `n` の階乗を返します。 + /// + /// # Panics + /// + /// `n >= size` の場合にパニックします。 + /// + /// # Examples + /// + /// ``` + /// use factorials::Factorial; + /// + /// let f = Factorial::new(10, 1000000007); + /// assert_eq!(f.factorial(0), 1); // 0! = 1 + /// assert_eq!(f.factorial(5), 120); // 5! = 120 + /// ``` pub fn factorial(&self, n: usize) -> u64 { assert!(n < self.factorial.len()); self.factorial[n] } + /// `n` の階乗の乗法逆元を返します。 + /// + /// # Panics + /// + /// `n >= size` の場合にパニックします。 + /// + /// # Examples + /// + /// ``` + /// use factorials::Factorial; + /// + /// let modulo = 1000000007; + /// let f = Factorial::new(10, modulo); + /// let n = 5; + /// assert_eq!((f.factorial(n) * f.inversion(n)) % modulo, 1); + /// ``` pub fn inversion(&self, n: usize) -> u64 { assert!(n < self.inversion_of_factorial.len()); self.inversion_of_factorial[n] } - /// 二項係数を返します。 + /// 二項係数 C(n, k) を返します。 + /// + /// 二項係数は nCk = n! / (k! * (n-k)!) で定義され、 + /// n 個の異なる要素から k 個を選ぶ組み合わせの数を表します。 /// /// # Examples /// @@ -103,6 +227,29 @@ impl Factorial { /// assert_eq!(f.binomial(4, 4), 1); /// ``` /// + /// ## 競技プログラミングでの応用例 + /// + /// ``` + /// use factorials::Factorial; + /// + /// let modulo = 1000000007; + /// let f = Factorial::new(200, modulo); + /// + /// // パスカルの三角形の性質: C(n, k) = C(n-1, k-1) + C(n-1, k) + /// let n = 10; + /// let k = 4; + /// let left = if k > 0 { f.binomial(n - 1, k - 1) } else { 0 }; + /// let right = f.binomial(n - 1, k); + /// assert_eq!(f.binomial(n, k), (left + right) % modulo); + /// + /// // 格子路問題: (0,0) から (m,n) への最短経路数 + /// let (m, n) = (5, 3); + /// let paths = f.binomial(m + n, m); // または f.binomial(m + n, n) + /// + /// // 重複組み合わせ: n種類から重複を許して k個選ぶ + /// let h_n_k = f.binomial(n + k - 1, k); + /// ``` + /// /// # Panics /// /// 以下の少なくともひとつが成り立つ場合パニックです。 @@ -126,11 +273,38 @@ impl Factorial { /// [`binomial`] とほとんど同じですが `n` が `k` より小さいときパニックせずに `0` を返します。 /// + /// 数学的に C(n, k) = 0 when n < k という性質を利用した安全なバージョンです。 + /// ループ処理などで境界チェックを省略したい場合に便利です。 + /// + /// # Examples + /// /// ``` /// use factorials::Factorial; /// /// let f = Factorial::new_checking_modulo_prime(5, 107); - /// assert_eq!(f.binomial_or_zero(3, 4), 0); + /// assert_eq!(f.binomial_or_zero(3, 4), 0); // n < k の場合 + /// assert_eq!(f.binomial_or_zero(4, 2), 6); // 通常の場合 + /// ``` + /// + /// ## 競技プログラミングでの使用例 + /// + /// ``` + /// use factorials::Factorial; + /// + /// let modulo = 1000000007; + /// let f = Factorial::new(100, modulo); + /// + /// // 動的プログラミングでの安全な使用 + /// let n = 10; + /// let mut dp = vec![0u64; n + 1]; + /// dp[0] = 1; + /// + /// for i in 1..=n { + /// for j in 0..=i { + /// // j > i の場合も安全に処理 + /// dp[i] = (dp[i] + f.binomial_or_zero(i - 1, j)) % modulo; + /// } + /// } /// ``` /// /// [`binomial`]: struct.Factorial.html#method.binomial diff --git a/algo/strongly_connected_components/src/lib.rs b/algo/strongly_connected_components/src/lib.rs index 463fa2d3..5388073a 100644 --- a/algo/strongly_connected_components/src/lib.rs +++ b/algo/strongly_connected_components/src/lib.rs @@ -1,6 +1,121 @@ -/// 強連結成分分解です。[参考](https://manabitimes.jp/math/1250) +//! 強連結成分分解 (Strongly Connected Components) を行うライブラリです。 +//! +//! 有向グラフの強連結成分分解を Kosaraju のアルゴリズムで実装しています。 +//! 強連結成分とは、任意の2頂点間で相互に到達可能な頂点の集合です。 +//! +//! # 計算量 +//! +//! - 時間計算量: O(V + E) +//! - 空間計算量: O(V + E) +//! +//! ここで V は頂点数、E は辺数です。 +//! +//! # 用途 +//! +//! - 有向グラフの強連結成分への分解 +//! - 2-SATの充足可能性判定 +//! - 有向グラフの凝縮(DAG化) +//! - 競技プログラミングでのグラフ問題解決 +//! +//! # Examples +//! +//! ## 基本的な使用例 +//! +//! ``` +//! use strongly_connected_components::strongly_connected_components; +//! +//! // 3つの頂点からなる循環グラフ: 0 -> 1 -> 2 -> 0 +//! let components = strongly_connected_components(3, &[(0, 1), (1, 2), (2, 0)]); +//! // 全体が1つの強連結成分 +//! assert_eq!(components.len(), 1); +//! let mut component = components[0].clone(); +//! component.sort(); +//! assert_eq!(component, vec![0, 1, 2]); +//! ``` +//! +//! ## 複数の強連結成分を持つグラフ +//! +//! ``` +//! use strongly_connected_components::strongly_connected_components; +//! +//! // グラフ: 0 -> 1 <-> 2, 3 -> 4 +//! let components = strongly_connected_components(5, &[(0, 1), (1, 2), (2, 1), (3, 4)]); +//! assert_eq!(components.len(), 4); // 4つの強連結成分 +//! +//! // 各成分のサイズを確認 +//! let sizes: Vec = components.iter().map(|c| c.len()).collect(); +//! assert!(sizes.contains(&1)); // 単独頂点の成分 +//! assert!(sizes.contains(&2)); // 2頂点の成分 {1, 2} +//! ``` + +/// 強連結成分分解を行います。[参考](https://manabitimes.jp/math/1250) +/// +/// Kosaraju のアルゴリズムを使用して、有向グラフを強連結成分に分解します。 +/// +/// # 引数 +/// +/// - `n`: 頂点数(頂点は 0, 1, ..., n-1 で番号付けされます) +/// - `edges`: 有向辺のリスト。各要素 `(u, v)` は頂点 u から頂点 v への辺を表します +/// +/// # 戻り値 +/// +/// `Vec>` を返します。各内側のベクタは1つの強連結成分を構成する頂点のリストです。 +/// 強連結成分はトポロジカル順序の逆順で返されます(後ろの成分から前の成分への辺は存在しません)。 +/// +/// # Examples +/// +/// ``` +/// use strongly_connected_components::strongly_connected_components; +/// +/// // 単純な循環グラフ +/// let scc = strongly_connected_components(3, &[(0, 1), (1, 2), (2, 0)]); +/// assert_eq!(scc.len(), 1); // 全体が1つの強連結成分 +/// +/// // 一方向のパス +/// let scc = strongly_connected_components(3, &[(0, 1), (1, 2)]); +/// assert_eq!(scc.len(), 3); // 各頂点が独立した強連結成分 +/// ``` +/// +/// # 2-SAT での使用例 +/// +/// ``` +/// use strongly_connected_components::strongly_connected_components; /// -/// 返り値を `components` とすると `components` の各要素は強連結成分をなす頂点のベクタです。 +/// // 2-SAT問題の例: (x1 ∨ ¬x2) ∧ (¬x1 ∨ x2) +/// // 変数の数 +/// let n_vars = 2; +/// // 含意グラフを構築: ¬A → B は (A ∨ B) と等価 +/// let mut edges = Vec::new(); +/// +/// // (x1 ∨ ¬x2) から ¬x1 → ¬x2, x2 → x1 +/// edges.push((1, 3)); // ¬x1(1) → ¬x2(3) +/// edges.push((2, 0)); // x2(2) → x1(0) +/// +/// // (¬x1 ∨ x2) から x1 → x2, ¬x2 → ¬x1 +/// edges.push((0, 2)); // x1(0) → x2(2) +/// edges.push((3, 1)); // ¬x2(3) → ¬x1(1) +/// +/// let components = strongly_connected_components(2 * n_vars, &edges); +/// +/// // 各変数 xi について、xi と ¬xi が同じ強連結成分にあるかチェック +/// let mut satisfiable = true; +/// for i in 0..n_vars { +/// let xi = i * 2; // xi のインデックス +/// let not_xi = i * 2 + 1; // ¬xi のインデックス +/// +/// // xi と ¬xi が同じ強連結成分にあると充足不可能 +/// let xi_component = components.iter().position(|c| c.contains(&xi)).unwrap(); +/// let not_xi_component = components.iter().position(|c| c.contains(¬_xi)).unwrap(); +/// +/// if xi_component == not_xi_component { +/// satisfiable = false; +/// break; +/// } +/// } +/// +/// // この例では充足可能 +/// assert!(satisfiable); +/// ``` pub fn strongly_connected_components(n: usize, edges: &[(usize, usize)]) -> Vec> { let mut graph = vec![vec![]; n]; for &(u, v) in edges { From bc8850d95d217a069a129a7d8792ba6d9967f2dc Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 28 Jul 2025 13:23:55 +0000 Subject: [PATCH 3/3] Complete comprehensive documentation improvements for all 10 algorithm libraries Co-authored-by: ia7ck <23146842+ia7ck@users.noreply.github.com> --- algo/arithmetic_series/src/lib.rs | 134 +++++++++++++++++++- algo/cumulative_sum_2d/src/lib.rs | 198 ++++++++++++++++++++++++++++++ algo/detect_cycle/src/lib.rs | 145 ++++++++++++++++++++-- algo/next_permutation/src/lib.rs | 172 +++++++++++++++++++++++++- algo/sliding_window/src/lib.rs | 152 ++++++++++++++++++++++- algo/union_find/src/lib.rs | 140 ++++++++++++++++++++- 6 files changed, 926 insertions(+), 15 deletions(-) diff --git a/algo/arithmetic_series/src/lib.rs b/algo/arithmetic_series/src/lib.rs index fbea9da0..ec7da4e1 100644 --- a/algo/arithmetic_series/src/lib.rs +++ b/algo/arithmetic_series/src/lib.rs @@ -1,7 +1,85 @@ +//! 等差数列の和を効率的に計算するライブラリです。 +//! +//! 等差数列 a, a+d, a+2d, ..., a+(n-1)d の和を公式を使って O(1) で計算します。 +//! オーバーフローチェック機能付きで、安全に大きな数値を扱えます。 +//! +//! # 数学的背景 +//! +//! 等差数列の和の公式: +//! ```text +//! S = n/2 * (2a + (n-1)d) = n/2 * (初項 + 末項) +//! ``` +//! +//! # 計算量 +//! +//! - 時間計算量: O(1) +//! - 空間計算量: O(1) +//! +//! # 用途 +//! +//! - 等差数列の和の高速計算 +//! - 数学的問題の解決 +//! - 競技プログラミングでの数列問題 +//! - 繰り返し処理の最適化 +//! +//! # Examples +//! +//! ## 基本的な使用例 +//! +//! ``` +//! use arithmetic_series::arithmetic_series; +//! +//! // 1 + 2 + 3 + ... + 10 +//! assert_eq!(arithmetic_series(1, 10, 1), Some(55)); +//! +//! // 2 + 4 + 6 + 8 + 10 (偶数の和) +//! assert_eq!(arithmetic_series(2, 5, 2), Some(30)); +//! +//! // 5 + 2 + (-1) + (-4) (減少数列) +//! assert_eq!(arithmetic_series(5, 4, -3), Some(2)); +//! ``` +//! +//! ## 競技プログラミングでの応用例 +//! +//! ``` +//! use arithmetic_series::arithmetic_series; +//! +//! // n(n+1)/2 の公式と同等 +//! fn triangular_number(n: i64) -> Option { +//! arithmetic_series(1, n, 1) +//! } +//! +//! // 平方数の和 1² + 2² + ... + n² = n(n+1)(2n+1)/6 +//! fn sum_of_squares_formula(n: i64) -> Option { +//! n.checked_mul(n + 1)? +//! .checked_mul(2 * n + 1)? +//! .checked_div(6) +//! } +//! +//! assert_eq!(triangular_number(100), Some(5050)); +//! assert_eq!(sum_of_squares_formula(3), Some(14)); // 1 + 4 + 9 +//! ``` + /// 初項 `a`, 項数 `n`, 公差 `d` の等差数列の和を求めます。 +/// 等差数列の和を計算します。 +/// +/// 初項 `a`, 項数 `n`, 公差 `d` の等差数列の和を公式 `n/2 * (2a + (n-1)d)` を使って計算します。 +/// 計算途中でオーバーフローが発生した場合は `None` を返します。 +/// +/// # 引数 +/// +/// - `a`: 等差数列の初項 +/// - `n`: 項数(0以上である必要があります) +/// - `d`: 公差 +/// +/// # 戻り値 +/// +/// - `Some(sum)`: 計算が成功した場合の和 +/// - `None`: オーバーフローが発生した場合 /// /// # Panics -/// if `n` is negative. +/// +/// `n` が負の場合にパニックします。 /// /// # Examples /// ``` @@ -13,6 +91,60 @@ /// assert_eq!(arithmetic_series(1, 5, 2), Some(25)); /// // 5 + 2 + (-1) + (-4) + (-7) + (-10) /// assert_eq!(arithmetic_series(5, 6, -3), Some(-15)); +/// +/// // 空の数列 +/// assert_eq!(arithmetic_series(42, 0, 3), Some(0)); +/// +/// // オーバーフローの例 +/// assert_eq!(arithmetic_series(1, std::i64::MAX, 1), None); +/// ``` +/// +/// ## 数学的応用例 +/// +/// ``` +/// use arithmetic_series::arithmetic_series; +/// +/// // ガウスの公式: 1 + 2 + ... + n = n(n+1)/2 +/// fn gauss_sum(n: i64) -> Option { +/// arithmetic_series(1, n, 1) +/// } +/// +/// // 奇数の和: 1 + 3 + 5 + ... + (2n-1) = n² +/// fn odd_sum(n: i64) -> Option { +/// arithmetic_series(1, n, 2) +/// } +/// +/// // 偶数の和: 2 + 4 + 6 + ... + 2n = n(n+1) +/// fn even_sum(n: i64) -> Option { +/// arithmetic_series(2, n, 2) +/// } +/// +/// assert_eq!(gauss_sum(100), Some(5050)); +/// assert_eq!(odd_sum(5), Some(25)); // 1+3+5+7+9 = 25 = 5² +/// assert_eq!(even_sum(4), Some(20)); // 2+4+6+8 = 20 = 4×5 +/// ``` +/// +/// ## 競技プログラミングでの実用例 +/// +/// ``` +/// use arithmetic_series::arithmetic_series; +/// +/// // 等差数列の部分和(l番目からr番目まで) +/// fn arithmetic_range_sum(a: i64, d: i64, l: i64, r: i64) -> Option { +/// if l > r { return Some(0); } +/// +/// let full_sum = arithmetic_series(a, r, d)?; +/// if l <= 1 { +/// Some(full_sum) +/// } else { +/// let prefix_sum = arithmetic_series(a, l - 1, d)?; +/// full_sum.checked_sub(prefix_sum) +/// } +/// } +/// +/// // テスト: 数列 [3, 5, 7, 9, 11] の 2番目から4番目まで +/// // = 5 + 7 + 9 = 21 +/// assert_eq!(arithmetic_range_sum(3, 2, 2, 4), Some(21)); /// ``` pub fn arithmetic_series(a: T, n: T, d: T) -> Option { if n == T::zero() { diff --git a/algo/cumulative_sum_2d/src/lib.rs b/algo/cumulative_sum_2d/src/lib.rs index e254a30a..e1106e49 100644 --- a/algo/cumulative_sum_2d/src/lib.rs +++ b/algo/cumulative_sum_2d/src/lib.rs @@ -1,7 +1,92 @@ +//! 二次元累積和(2D Cumulative Sum)のライブラリです。 +//! +//! 二次元配列に対して効率的な範囲和クエリを提供します。 +//! 前計算により、任意の矩形領域の合計を O(1) で計算できます。 +//! +//! # 計算量 +//! +//! - 前計算: O(H × W) +//! - 各クエリ: O(1) +//! +//! ここで H は行数、W は列数です。 +//! +//! # 用途 +//! +//! - 2D配列での矩形範囲の合計計算 +//! - 画像処理でのフィルタ計算 +//! - 競技プログラミングでの2次元クエリ問題 +//! - 動的プログラミングの最適化 +//! +//! # アルゴリズム +//! +//! 二次元累積和の構築と包除原理を使った範囲和計算: +//! ```text +//! sum(r1..r2, c1..c2) = S[r2-1][c2-1] - S[r1-1][c2-1] - S[r2-1][c1-1] + S[r1-1][c1-1] +//! ``` +//! +//! # Examples +//! +//! ## 基本的な使用例 +//! +//! ``` +//! use cumulative_sum_2d::CumulativeSum2D; +//! +//! let grid = vec![ +//! vec![1, 2, 3], +//! vec![4, 5, 6], +//! vec![7, 8, 9], +//! ]; +//! let cum_sum = CumulativeSum2D::new(&grid); +//! +//! // 全体の合計 +//! assert_eq!(cum_sum.sum(0..3, 0..3), 45); +//! +//! // 部分矩形の合計 +//! assert_eq!(cum_sum.sum(1..3, 1..3), 28); // 5+6+8+9 +//! assert_eq!(cum_sum.sum(0..2, 0..2), 12); // 1+2+4+5 +//! ``` +//! +//! ## 競技プログラミングでの応用例 +//! +//! ``` +//! use cumulative_sum_2d::CumulativeSum2D; +//! +//! // 最大部分矩形問題での使用例 +//! fn max_submatrix_sum(grid: &[Vec]) -> i32 { +//! let h = grid.len(); +//! let w = grid[0].len(); +//! let cum_sum = CumulativeSum2D::new(grid); +//! +//! let mut max_sum = i32::MIN; +//! for r1 in 0..h { +//! for r2 in r1+1..=h { +//! for c1 in 0..w { +//! for c2 in c1+1..=w { +//! let sum = cum_sum.sum(r1..r2, c1..c2); +//! max_sum = max_sum.max(sum); +//! } +//! } +//! } +//! } +//! max_sum +//! } +//! +//! let grid = vec![ +//! vec![1, -2, 3], +//! vec![-4, 5, -6], +//! vec![7, -8, 9], +//! ]; +//! let max_sum = max_submatrix_sum(&grid); +//! assert_eq!(max_sum, 9); // 右下の単一要素 +//! ``` + use std::ops::{Add, Range, Sub}; /// 二次元累積和です。 /// +/// 二次元配列に対して効率的な矩形範囲和クエリを提供するデータ構造です。 +/// 前計算により、任意の矩形領域の合計を O(1) で計算できます。 +/// /// # Examples /// ``` /// use cumulative_sum_2d::CumulativeSum2D; @@ -28,6 +113,41 @@ use std::ops::{Add, Range, Sub}; /// // . . . . . /// assert_eq!(cum_sum.sum(1..3, 1..4), 8); /// ``` +/// +/// ## 競技プログラミングでの典型的な使用パターン +/// +/// ``` +/// use cumulative_sum_2d::CumulativeSum2D; +/// +/// // imos法との組み合わせ +/// fn solve_2d_imos(h: usize, w: usize, updates: &[(usize, usize, usize, usize, i32)]) -> Vec> { +/// let mut grid = vec![vec![0; w]; h]; +/// +/// // imos法で更新を適用 +/// for &(r1, c1, r2, c2, val) in updates { +/// grid[r1][c1] += val; +/// if r2 < h { grid[r2][c1] -= val; } +/// if c2 < w { grid[r1][c2] -= val; } +/// if r2 < h && c2 < w { grid[r2][c2] += val; } +/// } +/// +/// // 累積和で実際の値を復元 +/// let cum_sum = CumulativeSum2D::new(&grid); +/// let mut result = vec![vec![0; w]; h]; +/// for i in 0..h { +/// for j in 0..w { +/// result[i][j] = cum_sum.sum(0..i+1, 0..j+1); +/// } +/// } +/// result +/// } +/// +/// // テスト: (0,0)-(2,2) に +1 を適用 +/// let updates = vec![(0, 0, 2, 2, 1)]; +/// let result = solve_2d_imos(3, 3, &updates); +/// assert_eq!(result[1][1], 1); // 範囲内 +/// assert_eq!(result[2][2], 0); // 範囲外 +/// ``` pub struct CumulativeSum2D { h: usize, w: usize, @@ -38,6 +158,28 @@ impl CumulativeSum2D where T: Clone + Copy + Default + Add + Sub, { + /// 二次元配列から累積和を構築します。 + /// + /// # Panics + /// + /// - `grid` が空の場合 + /// - `grid` の各行の長さが異なる場合 + /// + /// # Examples + /// + /// ``` + /// use cumulative_sum_2d::CumulativeSum2D; + /// + /// let grid = vec![ + /// vec![1, 2, 3], + /// vec![4, 5, 6], + /// ]; + /// let cum_sum = CumulativeSum2D::new(&grid); + /// + /// // 構築後、各種クエリが可能 + /// assert_eq!(cum_sum.sum(0..2, 0..3), 21); // 全体の合計 + /// assert_eq!(cum_sum.sum(0..1, 0..2), 3); // 上段左2要素 + /// ``` pub fn new(grid: &[Vec]) -> Self { let h = grid.len(); assert!(h >= 1); @@ -60,6 +202,62 @@ where Self { h, w, cum_sum } } + /// 指定された矩形範囲の合計を返します。 + /// + /// 包除原理を使用して、矩形 `(y_range.start, x_range.start)` から + /// `(y_range.end-1, x_range.end-1)` までの要素の合計を O(1) で計算します。 + /// + /// # 引数 + /// + /// - `y_range`: 行の範囲(半開区間) + /// - `x_range`: 列の範囲(半開区間) + /// + /// # 戻り値 + /// + /// 指定された矩形範囲内の要素の合計。範囲が空の場合は `T::default()`。 + /// + /// # Panics + /// + /// - `y_range.end > h` または `x_range.end > w` の場合 + /// + /// # Examples + /// + /// ``` + /// use cumulative_sum_2d::CumulativeSum2D; + /// + /// let grid = vec![ + /// vec![1, 2, 3], + /// vec![4, 5, 6], + /// vec![7, 8, 9], + /// ]; + /// let cum_sum = CumulativeSum2D::new(&grid); + /// + /// // 中央の2x2領域 + /// assert_eq!(cum_sum.sum(0..2, 1..3), 16); // 2+3+5+6 + /// + /// // 単一要素 + /// assert_eq!(cum_sum.sum(1..2, 1..2), 5); + /// + /// // 空の範囲 + /// assert_eq!(cum_sum.sum(1..1, 1..2), 0); + /// assert_eq!(cum_sum.sum(1..2, 1..1), 0); + /// ``` + /// + /// ## パフォーマンス比較 + /// + /// ``` + /// use cumulative_sum_2d::CumulativeSum2D; + /// + /// let large_grid = vec![vec![1; 1000]; 1000]; + /// let cum_sum = CumulativeSum2D::new(&large_grid); + /// + /// // O(1) での範囲和計算 + /// let sum1 = cum_sum.sum(100..200, 300..400); // 即座に計算 + /// let sum2 = cum_sum.sum(0..500, 0..600); // こちらも即座に計算 + /// + /// assert_eq!(sum1, 10000); // 100x100の領域 + /// assert_eq!(sum2, 300000); // 500x600の領域 + /// ``` pub fn sum(&self, y_range: Range, x_range: Range) -> T { let (y_start, y_end) = (y_range.start, y_range.end); let (x_start, x_end) = (x_range.start, x_range.end); diff --git a/algo/detect_cycle/src/lib.rs b/algo/detect_cycle/src/lib.rs index 4dcac88f..f75c650e 100644 --- a/algo/detect_cycle/src/lib.rs +++ b/algo/detect_cycle/src/lib.rs @@ -1,11 +1,78 @@ +//! グラフの閉路検出アルゴリズムのライブラリです。 +//! +//! 無向グラフと有向グラフの両方でサイクル(閉路)を効率的に検出します。 +//! 検出されたサイクルは辺のインデックスのリストとして返されます。 +//! +//! # 計算量 +//! +//! - 時間計算量: O(V + E) +//! - 空間計算量: O(V + E) +//! +//! ここで V は頂点数、E は辺数です。 +//! +//! # アルゴリズム +//! +//! - **無向グラフ**: DFS による後退辺の検出 +//! - **有向グラフ**: DFS による閉路検出(グレイ頂点への辺) +//! +//! # 用途 +//! +//! - グラフの閉路検出 +//! - DAG(有向非循環グラフ)の判定 +//! - 競技プログラミングでのグラフ問題 +//! - トポロジカルソートの前処理 +//! +//! # Examples +//! +//! ## 無向グラフでの閉路検出 +//! +//! ``` +//! use detect_cycle::detect_cycle_undirected; +//! +//! // 三角形グラフ: 0-1-2-0 +//! let cycle = detect_cycle_undirected(3, &[(0, 1), (1, 2), (2, 0)]); +//! assert!(cycle.is_some()); +//! assert_eq!(cycle.unwrap().len(), 3); // 3本の辺からなる閉路 +//! +//! // 木構造(閉路なし) +//! let no_cycle = detect_cycle_undirected(3, &[(0, 1), (1, 2)]); +//! assert!(no_cycle.is_none()); +//! ``` +//! +//! ## 有向グラフでの閉路検出 +//! +//! ``` +//! use detect_cycle::detect_cycle_directed; +//! +//! // 有向三角形: 0→1→2→0 +//! let cycle = detect_cycle_directed(3, &[(0, 1), (1, 2), (2, 0)]); +//! assert!(cycle.is_some()); +//! assert_eq!(cycle.unwrap().len(), 3); +//! +//! // DAG(閉路なし) +//! let no_cycle = detect_cycle_directed(3, &[(0, 1), (0, 2), (1, 2)]); +//! assert!(no_cycle.is_none()); +//! ``` + /// 無向グラフの閉路を求めます。 +/// 無向グラフの閉路を検出します。 +/// +/// DFS を使用して無向グラフ内の閉路を検出し、閉路を構成する辺のインデックスを返します。 +/// 閉路が複数存在する場合、そのうちの1つを返します。 +/// +/// # 引数 +/// +/// - `n`: 頂点数(頂点は 0, 1, ..., n-1 で番号付けされます) +/// - `edges`: 無向辺のリスト。各要素 `(u, v)` は頂点 u と頂点 v を結ぶ辺を表します /// -/// - `n`: 頂点数 -/// - `edges`: 辺 +/// # 戻り値 /// -/// 返り値は、閉路をなす辺の index のベクタです。 +/// - `Some(Vec)`: 閉路が存在する場合、閉路を構成する辺のインデックスのリスト +/// - `None`: 閉路が存在しない場合(つまり、グラフが森である場合) /// -/// # Example +/// 返される辺のインデックスは、`edges` スライス内での位置を示します。 +/// +/// # Examples /// ``` /// use detect_cycle::detect_cycle_undirected; /// // 0 1 3 @@ -29,6 +96,23 @@ /// ]; /// assert!(candidates.contains(&cycle)); /// ``` +/// +/// ## 競技プログラミングでの応用例 +/// +/// ``` +/// use detect_cycle::detect_cycle_undirected; +/// +/// // グラフが木かどうかの判定 +/// fn is_tree(n: usize, edges: &[(usize, usize)]) -> bool { +/// // 木の条件: 連結 かつ 辺数 = 頂点数 - 1 かつ 閉路なし +/// edges.len() == n - 1 && detect_cycle_undirected(n, edges).is_none() +/// } +/// +/// // テストケース +/// assert!(is_tree(4, &[(0, 1), (1, 2), (1, 3)])); // 木 +/// assert!(!is_tree(4, &[(0, 1), (1, 2), (2, 3), (3, 0)])); // 閉路あり +/// assert!(!is_tree(4, &[(0, 1), (2, 3)])); // 非連結 +/// ``` pub fn detect_cycle_undirected(n: usize, edges: &[(usize, usize)]) -> Option> { fn dfs( curr: usize, @@ -89,13 +173,25 @@ pub fn detect_cycle_undirected(n: usize, edges: &[(usize, usize)]) -> Option)`: 閉路が存在する場合、閉路を構成する辺のインデックスのリスト +/// - `None`: 閉路が存在しない場合(つまり、グラフが DAG である場合) +/// +/// 返される辺のインデックスは、`edges` スライス内での位置を示します。 +/// 辺のリストは閉路の順序で並んでいます。 +/// +/// # Examples /// ``` /// use detect_cycle::detect_cycle_directed; /// @@ -110,6 +206,39 @@ pub fn detect_cycle_undirected(n: usize, edges: &[(usize, usize)]) -> Option bool { +/// detect_cycle_directed(n, edges).is_none() +/// } +/// +/// // 依存関係グラフでの循環依存検出 +/// fn has_circular_dependency( +/// tasks: usize, +/// dependencies: &[(usize, usize)] +/// ) -> Option> { +/// // (a, b) = タスク a がタスク b に依存 +/// detect_cycle_directed(tasks, dependencies) +/// } +/// +/// // テストケース +/// assert!(is_dag(3, &[(0, 1), (1, 2)])); // DAG +/// assert!(!is_dag(3, &[(0, 1), (1, 2), (2, 0)])); // 閉路あり +/// +/// // 循環依存のあるタスク +/// let deps = vec![(0, 1), (1, 2), (2, 0)]; // 0→1→2→0 +/// assert!(has_circular_dependency(3, &deps).is_some()); +/// ``` +/// +/// # アルゴリズムの詳細 +/// +/// このアルゴリズムは DFS を使用して「グレイ」状態の頂点(現在の DFS パス上にある頂点) +/// への辺を検出することで閉路を見つけます。これは有向グラフでの標準的な閉路検出手法です。 pub fn detect_cycle_directed(n: usize, edges: &[(usize, usize)]) -> Option> { fn dfs( curr: usize, diff --git a/algo/next_permutation/src/lib.rs b/algo/next_permutation/src/lib.rs index 13fd8bec..348dd267 100644 --- a/algo/next_permutation/src/lib.rs +++ b/algo/next_permutation/src/lib.rs @@ -1,12 +1,106 @@ +//! Next Permutation アルゴリズムのライブラリです。 +//! +//! 辞書順で次の順列を効率的に生成するアルゴリズムを提供します。 +//! C++ の `std::next_permutation` と同等の機能を Rust で実装しています。 +//! +//! # 計算量 +//! +//! - 時間計算量: O(n) (最悪ケース) +//! - 空間計算量: O(1) (in-place で実行) +//! +//! # アルゴリズム +//! +//! 1. 右から左に向かって、隣接する要素が増加している最初の位置を見つける +//! 2. その位置より右側で、基準要素より大きい最小の要素を見つける +//! 3. 2つの要素を交換 +//! 4. 基準位置より右側の部分を逆順にする +//! +//! # 用途 +//! +//! - 順列の全列挙 +//! - 辞書順での順列生成 +//! - 競技プログラミングでの全探索 +//! - 組み合わせ最適化問題 +//! +//! # Examples +//! +//! ## 基本的な使用例 +//! +//! ``` +//! use next_permutation::NextPermutation; +//! +//! let mut v = vec![1, 2, 3]; +//! +//! // 順列を順番に生成 +//! assert_eq!(v, vec![1, 2, 3]); +//! assert!(v.next_permutation()); +//! assert_eq!(v, vec![1, 3, 2]); +//! assert!(v.next_permutation()); +//! assert_eq!(v, vec![2, 1, 3]); +//! assert!(v.next_permutation()); +//! assert_eq!(v, vec![2, 3, 1]); +//! assert!(v.next_permutation()); +//! assert_eq!(v, vec![3, 1, 2]); +//! assert!(v.next_permutation()); +//! assert_eq!(v, vec![3, 2, 1]); +//! assert!(!v.next_permutation()); // これ以上の順列はない +//! ``` +//! +//! ## 競技プログラミングでの応用例 +//! +//! ``` +//! use next_permutation::NextPermutation; +//! +//! // 全順列での最適解探索 +//! fn solve_with_permutation(items: &[i32]) -> i32 { +//! let mut perm = items.to_vec(); +//! perm.sort(); // 辞書順最小から開始 +//! +//! let mut best_score = i32::MIN; +//! loop { +//! // 現在の順列での評価 +//! let score = evaluate_permutation(&perm); +//! best_score = best_score.max(score); +//! +//! if !perm.next_permutation() { +//! break; +//! } +//! } +//! best_score +//! } +//! +//! fn evaluate_permutation(perm: &[i32]) -> i32 { +//! // 例:隣接要素の差の絶対値の和 +//! perm.windows(2).map(|w| (w[1] - w[0]).abs()).sum() +//! } +//! +//! let items = vec![1, 3, 2]; +//! let result = solve_with_permutation(&items); +//! assert!(result > 0); +//! ``` + /// next permutation です。 +/// Next Permutation アルゴリズムを提供するトレイトです。 /// +/// スライス型に対してnext permutationアルゴリズムを適用し、 +/// 辞書順で次の順列を生成する機能を提供します。 +/// /// [実装の参考資料](https://ngtkana.hatenablog.com/entry/2021/11/08/000209) pub trait NextPermutation { fn next_permutation(&mut self) -> bool; } impl NextPermutation for [T] { - /// 数列を辞書順でひとつ進めます。進めなかったら false を返します。 + /// 数列を辞書順でひとつ進めます。 + /// + /// 現在の順列を辞書順で次の順列に変更します。 + /// 次の順列が存在しない場合(つまり、現在の順列が辞書順で最大の場合)は + /// 配列を変更せずに `false` を返します。 + /// + /// # 戻り値 + /// + /// - `true`: 次の順列に正常に進んだ場合 + /// - `false`: 次の順列が存在しない場合(辞書順最大に到達) /// /// # Examples /// ``` @@ -19,6 +113,82 @@ impl NextPermutation for [T] { /// let mut a = vec![3, 2, 1]; /// assert!(!a.next_permutation()); /// ``` + /// + /// ## 全順列の生成例 + /// + /// ``` + /// use next_permutation::NextPermutation; + /// + /// let mut permutations = Vec::new(); + /// let mut current = vec![1, 2, 3]; + /// + /// loop { + /// permutations.push(current.clone()); + /// if !current.next_permutation() { + /// break; + /// } + /// } + /// + /// assert_eq!(permutations, vec![ + /// vec![1, 2, 3], + /// vec![1, 3, 2], + /// vec![2, 1, 3], + /// vec![2, 3, 1], + /// vec![3, 1, 2], + /// vec![3, 2, 1], + /// ]); + /// ``` + /// + /// ## 重複要素がある場合 + /// + /// ``` + /// use next_permutation::NextPermutation; + /// + /// let mut v = vec![1, 1, 2]; + /// let mut perms = vec![v.clone()]; + /// + /// while v.next_permutation() { + /// perms.push(v.clone()); + /// } + /// + /// assert_eq!(perms, vec![ + /// vec![1, 1, 2], + /// vec![1, 2, 1], + /// vec![2, 1, 1], + /// ]); + /// ``` + /// + /// ## 競技プログラミングでの使用例 + /// + /// ``` + /// use next_permutation::NextPermutation; + /// + /// // n人の並び方での最適解を求める + /// fn solve_arrangement_problem(scores: &[i32]) -> i32 { + /// let mut arrangement: Vec = (0..scores.len()).collect(); + /// let mut best_score = 0; + /// + /// loop { + /// // 現在の並び方での得点計算 + /// let current_score: i32 = arrangement.iter() + /// .enumerate() + /// .map(|(pos, &person)| scores[person] * (pos as i32 + 1)) + /// .sum(); + /// + /// best_score = best_score.max(current_score); + /// + /// if !arrangement.next_permutation() { + /// break; + /// } + /// } + /// best_score + /// } + /// + /// let scores = vec![3, 1, 4]; // 各人の基礎点 + /// let result = solve_arrangement_problem(&scores); + /// // 最適な並び方での得点 + /// assert!(result > 0); + /// ``` fn next_permutation(&mut self) -> bool { if self.len() <= 1 { return false; diff --git a/algo/sliding_window/src/lib.rs b/algo/sliding_window/src/lib.rs index d010cec7..270c9eb1 100644 --- a/algo/sliding_window/src/lib.rs +++ b/algo/sliding_window/src/lib.rs @@ -1,8 +1,76 @@ +//! スライディングウィンドウを使った最小値・最大値計算のライブラリです。 +//! +//! 固定サイズの窓をスライドさせながら、各窓での最小値または最大値を効率的に計算します。 +//! デックを使用したアルゴリズムにより、全体で O(n) の時間計算量を実現します。 +//! +//! # 計算量 +//! +//! - 時間計算量: O(n)(全体) +//! - 空間計算量: O(k) ここで k は窓のサイズ +//! +//! # アルゴリズム +//! +//! モノトニックデック(単調デック)を使用します: +//! - 最小値の場合: デック内で値が単調増加になるよう維持 +//! - 最大値の場合: デック内で値が単調減少になるよう維持 +//! +//! # 用途 +//! +//! - 固定サイズ窓での最小値・最大値クエリ +//! - 動的プログラミングの最適化 +//! - 競技プログラミングでのスライディングウィンドウ問題 +//! - 時系列データの解析 +//! +//! # Examples +//! +//! ## 基本的な使用例 +//! +//! ``` +//! use sliding_window::{sliding_window_minimum, sliding_window_maximum}; +//! +//! let data = vec![3, 1, 4, 1, 5, 9, 2, 6]; +//! +//! // 窓サイズ3での最小値 +//! let minimums = sliding_window_minimum(&data, 3); +//! assert_eq!(minimums, vec![&1, &1, &1, &1, &2, &2]); +//! +//! // 窓サイズ3での最大値 +//! let maximums = sliding_window_maximum(&data, 3); +//! assert_eq!(maximums, vec![&4, &4, &5, &9, &9, &6]); +//! ``` +//! +//! ## 競技プログラミングでの応用例 +//! +//! ``` +//! use sliding_window::sliding_window_minimum; +//! +//! // 最小コストでk個連続する要素の和を最小化 +//! fn min_subarray_sum(arr: &[i32], k: usize) -> i32 { +//! if arr.len() < k { return 0; } +//! +//! // 窓サイズkでの合計を計算 +//! let mut window_sum = arr[0..k].iter().sum::(); +//! let mut min_sum = window_sum; +//! +//! for i in k..arr.len() { +//! window_sum += arr[i] - arr[i - k]; +//! min_sum = min_sum.min(window_sum); +//! } +//! min_sum +//! } +//! +//! let arr = vec![2, 1, 4, 9, 2, 5, 1, 3]; +//! assert_eq!(min_subarray_sum(&arr, 3), 7); // [2, 1, 4] または [2, 5, 1] など +//! ``` + use std::collections::VecDeque; /// 幅 `window_width` の区間すべてに対し最小値を求めます。 /// -/// 配列 `a` に対し次で定める配列 `b` を求めます。 +/// 配列 `a` に対してスライディングウィンドウを適用し、 +/// 各位置での窓内最小値を効率的に計算します。 +/// +/// 配列 `a` に対し次で定める配列 `b` を求めます: /// /// - `a` の長さ `a.len()` を `n` とする /// - `b[0]`: `min(a[0], a[1], ..., a[window_width - 1])` @@ -10,11 +78,14 @@ use std::collections::VecDeque; /// - ... /// - `b[n - window_width]`: `min(a[n - window_width], ..., a[n - 2], a[n - 1])` /// +/// # アルゴリズム +/// +/// モノトニックデック(単調増加デック)を使用して、各窓での最小値を O(1) で取得します。 /// [実装の参考資料](https://qiita.com/kuuso1/items/318d42cd089a49eeb332) /// /// # Panics /// -/// if `window_width` is zero or is greater than `a.len()`. +/// `window_width` が 0 または `a.len()` より大きい場合にパニックします。 /// /// # Examples /// @@ -36,6 +107,29 @@ use std::collections::VecDeque; /// ] /// ); /// ``` +/// +/// ## 競技プログラミングでの応用例 +/// +/// ``` +/// use sliding_window::sliding_window_minimum; +/// +/// // 配列の各k個連続部分の最小値と最大値の差の最大値を求める +/// fn max_min_difference(arr: &[i32], k: usize) -> i32 { +/// if arr.len() < k { return 0; } +/// +/// let minimums = sliding_window_minimum(arr, k); +/// let maximums = sliding_window::sliding_window_maximum(arr, k); +/// +/// minimums.iter().zip(maximums.iter()) +/// .map(|(min, max)| **max - **min) +/// .max() +/// .unwrap_or(0) +/// } +/// +/// let arr = vec![1, 3, 2, 7, 5, 1, 4]; +/// let max_diff = max_min_difference(&arr, 3); +/// assert_eq!(max_diff, 6); // [2, 7, 5] の差 7-2=5 など +/// ``` pub fn sliding_window_minimum(a: &[T], window_width: usize) -> Vec<&T> where T: Ord, @@ -43,7 +137,59 @@ where sliding_window(a, window_width, |last, new| last >= new) } -/// [`sliding_window_minimum`](fn.sliding_window_minimum.html) の最大値バージョンです。 +/// [`sliding_window_minimum`] の最大値バージョンです。 +/// +/// 幅 `window_width` の区間すべてに対し最大値を求めます。 +/// アルゴリズムは最小値版と同様ですが、モノトニックデックを単調減少で維持します。 +/// +/// # Examples +/// +/// ``` +/// use sliding_window::sliding_window_maximum; +/// +/// let a = vec![4, 7, 7, 8, 5, 7, 6, 9, 9, 2, 8, 3]; +/// let maximums = sliding_window_maximum(&a, 4); +/// assert_eq!( +/// maximums, +/// vec![ +/// &8, // 4 7 7 8 +/// &8, // 7 7 8 5 +/// &8, // 7 8 5 7 +/// &8, // 8 5 7 6 +/// &9, // 5 7 6 9 +/// &9, // 7 6 9 9 +/// &9, // 6 9 9 2 +/// &9, // 9 9 2 8 +/// &8, // 9 2 8 3 +/// ] +/// ); +/// ``` +/// +/// ## Range Minimum/Maximum Query (RMQ) での使用 +/// +/// ``` +/// use sliding_window::{sliding_window_minimum, sliding_window_maximum}; +/// +/// // 固定サイズ窓での RMQ を前計算 +/// fn precompute_rmq(arr: &[i32], window_size: usize) -> (Vec, Vec) { +/// let mins: Vec = sliding_window_minimum(arr, window_size) +/// .into_iter().map(|&x| x).collect(); +/// let maxs: Vec = sliding_window_maximum(arr, window_size) +/// .into_iter().map(|&x| x).collect(); +/// (mins, maxs) +/// } +/// +/// let data = vec![3, 1, 4, 1, 5, 9, 2, 6, 5, 3]; +/// let (mins, maxs) = precompute_rmq(&data, 3); +/// +/// // 各窓での最小値・最大値が前計算されている +/// assert_eq!(mins[0], 1); // [3,1,4] の最小値 +/// assert_eq!(maxs[0], 4); // [3,1,4] の最大値 +/// assert_eq!(mins[3], 1); // [1,5,9] の最小値 +/// assert_eq!(maxs[3], 9); // [1,5,9] の最大値 +/// ``` +/// +/// [`sliding_window_minimum`]: fn.sliding_window_minimum.html pub fn sliding_window_maximum(a: &[T], window_width: usize) -> Vec<&T> where T: Ord, diff --git a/algo/union_find/src/lib.rs b/algo/union_find/src/lib.rs index cb727415..4f7fef35 100644 --- a/algo/union_find/src/lib.rs +++ b/algo/union_find/src/lib.rs @@ -1,3 +1,99 @@ +//! Union Find(素集合データ構造)のライブラリです。 +//! +//! Union Find は互いに素な集合を効率的に管理するデータ構造です。 +//! 主に以下の操作を高速に実行できます: +//! - 2つの要素が同じ集合に属するかの判定 +//! - 2つの集合の統合 +//! - 各集合のサイズの取得 +//! +//! # 計算量 +//! +//! - 初期化: O(n) +//! - 各操作(unite, find, same, size): ほぼ O(α(n)) +//! - α(n) は逆アッカーマン関数(実用上は定数) +//! +//! # 最適化技法 +//! +//! - **経路圧縮**: find 操作時に親へのパスを圧縮 +//! - **union by size**: 統合時に小さい木を大きい木に接続 +//! +//! # 用途 +//! +//! - グラフの連結性判定 +//! - 最小全域木アルゴリズム(Kruskal法) +//! - 同値関係の管理 +//! - 競技プログラミングでのグラフ・集合問題 +//! +//! # Examples +//! +//! ## 基本的な使用例 +//! +//! ``` +//! use union_find::UnionFind; +//! +//! let mut uf = UnionFind::new(5); +//! +//! // 集合の統合 +//! uf.unite(0, 1); +//! uf.unite(2, 3); +//! +//! // 連結性の確認 +//! assert!(uf.same(0, 1)); +//! assert!(!uf.same(0, 2)); +//! +//! // 集合のサイズ +//! assert_eq!(uf.size(0), 2); // {0, 1} +//! assert_eq!(uf.size(2), 2); // {2, 3} +//! assert_eq!(uf.size(4), 1); // {4} +//! +//! // 連結成分の数 +//! assert_eq!(uf.count_groups(), 3); +//! ``` +//! +//! ## Kruskal法での最小全域木 +//! +//! ``` +//! use union_find::UnionFind; +//! +//! // 辺を重みでソート: (重み, 始点, 終点) +//! let mut edges = vec![(1, 0, 1), (2, 1, 2), (3, 0, 2), (4, 2, 3)]; +//! edges.sort_by_key(|&(w, _, _)| w); +//! +//! let n = 4; // 頂点数 +//! let mut uf = UnionFind::new(n); +//! let mut mst_weight = 0; +//! let mut mst_edges = Vec::new(); +//! +//! for (weight, u, v) in edges { +//! if uf.unite(u, v) { +//! mst_weight += weight; +//! mst_edges.push((u, v)); +//! } +//! } +//! +//! assert_eq!(mst_weight, 7); // 1 + 2 + 4 +//! assert_eq!(mst_edges.len(), n - 1); // n-1 本の辺 +//! ``` +//! +//! ## グラフの連結成分数の計算 +//! +//! ``` +//! use union_find::UnionFind; +//! +//! fn count_connected_components(n: usize, edges: &[(usize, usize)]) -> usize { +//! let mut uf = UnionFind::new(n); +//! for &(u, v) in edges { +//! uf.unite(u, v); +//! } +//! uf.count_groups() +//! } +//! +//! // グラフの例: 0-1, 2-3 の2つの連結成分 +//! let edges = vec![(0, 1), (2, 3)]; +//! assert_eq!(count_connected_components(5, &edges), 3); +//! // 連結成分: {0,1}, {2,3}, {4} +//! ``` + /// Union Find はグラフの連結成分を管理します。 #[derive(Clone, Debug)] pub struct UnionFind { @@ -12,7 +108,19 @@ enum NodeKind { } impl UnionFind { - /// 頂点数を `n` として初期化します。 + /// 指定された頂点数で Union Find を初期化します。 + /// + /// 初期状態では各頂点が独立した連結成分を形成します。 + /// + /// # Examples + /// + /// ``` + /// use union_find::UnionFind; + /// + /// let uf = UnionFind::new(5); + /// // 初期状態: {0}, {1}, {2}, {3}, {4} の5つの連結成分 + /// assert_eq!(uf.count_groups(), 5); + /// ``` pub fn new(n: usize) -> Self { Self { nodes: vec![NodeKind::Root { size: 1 }; n], @@ -132,7 +240,7 @@ impl UnionFind { } /// 頂点 `i` と頂点 `j` が同じ連結成分に属するかどうかを返します。 - /// + /// /// # Examples /// /// ``` @@ -151,6 +259,34 @@ impl UnionFind { /// assert!(uf.same(1, 2)); /// assert!(uf.same(0, 2)); /// assert!(uf.same(3, 4)); + /// + /// assert!(!uf.same(0, 3)); + /// assert!(!uf.same(2, 5)); + /// ``` + /// + /// ## 競技プログラミングでの応用例 + /// + /// ``` + /// use union_find::UnionFind; + /// + /// // クエリ処理の例 + /// fn process_queries(n: usize, queries: &[(i32, usize, usize)]) -> Vec { + /// let mut uf = UnionFind::new(n); + /// let mut results = Vec::new(); + /// + /// for &(query_type, u, v) in queries { + /// match query_type { + /// 1 => { uf.unite(u, v); }, // 統合クエリ + /// 2 => { results.push(uf.same(u, v)); }, // 判定クエリ + /// _ => {} + /// } + /// } + /// results + /// } + /// + /// let queries = vec![(1, 0, 1), (1, 1, 2), (2, 0, 2), (2, 0, 3)]; + /// let results = process_queries(4, &queries); + /// assert_eq!(results, vec![true, false]); // 0と2は連結、0と3は非連結 /// ``` pub fn same(&mut self, i: usize, j: usize) -> bool { self.find(i) == self.find(j)