Skip to content
Closed
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
134 changes: 133 additions & 1 deletion algo/arithmetic_series/src/lib.rs
Original file line number Diff line number Diff line change
@@ -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<i64> {
//! arithmetic_series(1, n, 1)
//! }
//!
//! // 平方数の和 1² + 2² + ... + n² = n(n+1)(2n+1)/6
//! fn sum_of_squares_formula(n: i64) -> Option<i64> {
//! 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
/// ```
Expand All @@ -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<i64> {
/// arithmetic_series(1, n, 1)
/// }
///
/// // 奇数の和: 1 + 3 + 5 + ... + (2n-1) = n²
/// fn odd_sum(n: i64) -> Option<i64> {
/// arithmetic_series(1, n, 2)
/// }
///
/// // 偶数の和: 2 + 4 + 6 + ... + 2n = n(n+1)
/// fn even_sum(n: i64) -> Option<i64> {
/// 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<i64> {
/// 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<T: Int>(a: T, n: T, d: T) -> Option<T> {
if n == T::zero() {
Expand Down
133 changes: 120 additions & 13 deletions algo/auxiliary_tree/src/lib.rs
Original file line number Diff line number Diff line change
@@ -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<usize> = (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],
Expand Down
Loading
Loading