diff --git a/src/project_euler/mod.rs b/src/project_euler/mod.rs index 3b9dc95..464110d 100644 --- a/src/project_euler/mod.rs +++ b/src/project_euler/mod.rs @@ -1,10 +1,10 @@ pub mod prob17; pub mod prob49; -pub mod prob491; pub mod prob497; pub mod prob61; pub mod prob650; pub mod prob66; +pub mod prob719; pub mod prob885; pub mod prob932; pub mod prob934; diff --git a/src/project_euler/prob719.rs b/src/project_euler/prob719.rs new file mode 100644 index 0000000..15706af --- /dev/null +++ b/src/project_euler/prob719.rs @@ -0,0 +1,579 @@ +/// Number Splitting +/// +/// We define an S-number to be a natural number, n, that is a perfect square and its square root can be obtained by splitting the decimal representation of n into k or more numbers then adding the numbers. +/// +/// For example, 81 is an S-number because \(\sqrt{81} = 8 + 1\). +/// +/// 6724 is an S-number: \(\sqrt{6724} = 6 + 72 + 4\). +/// +/// 8281 is an S-number: \(\sqrt{8281} = 8 + 2 + 81\). +/// +/// 9801 is an S-number: \(\sqrt{9801} = 98 + 0 + 1\). +/// +/// T(N) is the sum of all S-numbers n <= N. +/// +/// You are given T(10^4) = 41333. +/// Find T(10^12). +/// +use crate::utils::{ + combinatoric::{composition, composition_with_min_max}, + integer::is_square, +}; +use itertools::Itertools; +use std::collections::HashSet; + +fn split_number(digits: &Vec, comp: Vec<&usize>) -> Vec { + comp.iter() + .fold((0, Vec::new()), |(cursor, mut result), &c| { + result.push( + digits[cursor..cursor + c] + .iter() + .fold(0, |num, &d| num * 10 + d), + ); + (cursor + c, result) + }) + .1 +} + +fn is_s_number(n: usize) -> bool { + if !is_square(n) { + return false; + } + + let square_root = (n as f64).sqrt() as u32; + let digits = n + .to_string() + .chars() + .map(|c| c.to_digit(10).unwrap()) + .collect::>(); + let compositions = composition(digits.len()); + for comp in compositions { + for permuted_comp in comp.iter().permutations(comp.len()) { + let split_numbers = split_number(&digits, permuted_comp); + if split_numbers.iter().sum::() == square_root { + return true; + } + } + } + false +} + +pub fn list_of_s_numbers(iter: T) -> Vec +where + T: IntoIterator, +{ + iter.into_iter().filter(|n| is_s_number(*n)).collect() +} + +pub fn sum_of_s_numbers(iter: T) -> usize +where + T: IntoIterator, +{ + iter.into_iter().filter(|n| is_s_number(*n)).sum() +} + +pub fn sum_of_s_numbers_up_to(n: usize) -> usize { + let sqrt_n = (n as f64).sqrt() as usize; + sum_of_s_numbers((1..=sqrt_n).map(|i| i * i)) +} + +pub fn list_of_s_numbers_up_to(n: usize) -> Vec { + let sqrt_n = (n as f64).sqrt() as usize; + list_of_s_numbers((1..=sqrt_n).map(|i| i * i)) +} + +fn can_concatenate_to(n: usize, num_vec: &Vec, digit: usize) -> bool { + for permuted_num_vec in num_vec.iter().permutations(num_vec.len()) { + let concatenated_num = permuted_num_vec + .iter() + .fold(String::new(), |acc, num| format!("{}{}", acc, num)) + .parse::() + .unwrap(); + let concatenated_num_digit = concatenated_num.to_string().len(); + if concatenated_num == n && concatenated_num_digit == digit { + return true; + } + } + false +} + +pub fn list_of_s_numbers_of_digit(digit: usize) -> HashSet { + fn number_of_digit(d: usize) -> Vec { + if d == 1 { + return vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; + } + let start = 10u32.pow(d as u32 - 1); + let end = 10u32.pow(d as u32); + (start..end).map(|i| i as usize).collect() + } + let mut result = HashSet::new(); + + for comp in composition(digit) { + let sum_max: u64 = comp.iter().map(|d| 10u64.pow(*d as u32) - 1).sum(); + if sum_max * sum_max <= 10u64.pow(digit as u32 - 1) { + continue; + } + + for num_vec in comp + .iter() + .map(|c| number_of_digit(*c)) + .multi_cartesian_product() + { + let sum = num_vec.iter().sum::(); + if can_concatenate_to(sum * sum, &num_vec, digit) { + result.insert(sum * sum); + } + } + } + result +} + +#[derive(Debug, Clone)] +pub struct SNumber { + pub composition: Vec, + pub digits: Vec>, +} + +pub fn digit_to_num<'a, T>(digits: T) -> usize +where + T: IntoIterator, +{ + digits.into_iter().fold(0, |acc, d| acc * 10 + *d as usize) +} + +pub fn digit_to_num_owned(digits: T) -> usize +where + T: IntoIterator, +{ + digits.into_iter().fold(0, |acc, d| acc * 10 + d as usize) +} + +impl SNumber { + fn new(composition: &Vec) -> Self { + let length = composition.iter().sum::(); + Self { + composition: composition.clone(), + digits: vec![None; length], + } + } + + fn check(&self) -> Option { + if self.digits.iter().any(|d| d.is_none()) { + return None; + } + + let digits = self.digits.iter().map(|d| d.unwrap()).collect::>(); + let num = digit_to_num(&digits[..]); + let comp_sum = self + .composition + .iter() + .fold((0, 0), |(sum, cursor), d| { + (sum + digit_to_num(&digits[cursor..cursor + d]), cursor + d) + }) + .0; + if comp_sum * comp_sum == num { + Some(num) + } else { + None + } + } + + fn min_digit(&self) -> Vec { + self.digits + .iter() + .enumerate() + .map(|(i, d)| { + if i == 0 { + d.unwrap_or(1) + } else { + d.unwrap_or(0) + } + }) + .collect() + } + + fn max_digit(&self) -> Vec { + self.digits.iter().map(|d| d.unwrap_or(9)).collect() + } + + fn comp_sum(&self, digits: &Vec) -> usize { + self.composition + .iter() + .fold((0, 0), |(sum, cursor), d| { + (sum + digit_to_num(&digits[cursor..cursor + d]), cursor + d) + }) + .0 + } + + fn range_comp_sum(&self) -> (usize, usize) { + let sum_min = self.comp_sum(&self.min_digit()); + let sum_max = self.comp_sum(&self.max_digit()); + (sum_min, sum_max) + } + + fn range_num(&self) -> (usize, usize) { + let num_min = digit_to_num_owned(self.min_digit()); + let num_max = digit_to_num_owned(self.max_digit()); + (num_min, num_max) + } + + fn has_common_range(&self) -> bool { + let (num_min, num_max) = self.range_num(); + let (sum_min, sum_max) = self.range_comp_sum(); + num_min <= sum_max * sum_max && num_max >= sum_min * sum_min + } + + fn dominant_digit(&self) -> usize { + let mut place_vec = vec![1; self.digits.len()]; + let mut cursor = 0; + for c in self.composition.iter() { + let mut place = 10i32.pow(*c as u32 - 1); + for _ in 0..*c { + place_vec[cursor] = place; + place /= 10; + cursor += 1; + } + } + self.digits + .iter() + .zip(place_vec.iter()) + .enumerate() + .max_by_key(|(i, (d, p))| match d { + Some(_) => (-1, 0), + None => (**p, -(*i as i32)), + }) + .unwrap() + .0 + } + + fn find_solution(&mut self) -> HashSet { + if !self.has_common_range() { + return HashSet::new(); + } else if let Some(num) = self.check() { + return HashSet::from([num]); + } + + let dominant_digit = self.dominant_digit(); + let mut result = HashSet::new(); + let candidates = if dominant_digit == 0 { 1..=9 } else { 0..=9 }; + + for candidate in candidates { + self.digits[dominant_digit] = Some(candidate); + result.extend(self.find_solution()); + self.digits[dominant_digit] = None; + } + + result + } +} + +pub fn list_of_s_numbers_final(digit: usize) -> HashSet { + let mut result = HashSet::new(); + let mut visited = HashSet::new(); + for i in 2..=digit { + let minmax = (i - 1) / 2; + for comp in composition_with_min_max(i, minmax) { + let s_number = SNumber::new(&comp); + if !s_number.has_common_range() { + continue; + } + let length = comp.len(); + for perm in comp.into_iter().permutations(length) { + if visited.contains(&perm) { + continue; + } + visited.insert(perm.clone()); + let mut s_number = SNumber::new(&perm); + result.extend(s_number.find_solution()); + } + } + } + result +} + +fn is_s_number_answer(comp_sum: i64, concatenated_num: i64) -> bool { + if concatenated_num < comp_sum || comp_sum < 0 { + return false; + } else if concatenated_num == comp_sum { + return true; + } + + let mut scissor = 10; + while scissor < concatenated_num { + let remain_concatenated_num = concatenated_num / scissor; + let sliced = concatenated_num % scissor; + if is_s_number_answer(comp_sum - sliced, remain_concatenated_num) { + return true; + } + scissor *= 10; + } + false +} + +pub fn sum_of_s_numbers_answer(n: i64) -> i64 { + let max_comp_sum = n.isqrt(); + let mut sum = 0; + for comp_sum in 2..=max_comp_sum { + if is_s_number_answer(comp_sum, comp_sum * comp_sum) { + sum += comp_sum * comp_sum; + } + } + sum +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_split_number() { + let digits = vec![1, 3, 4, 5]; + + let comp = vec![&2, &2]; + assert_eq!(split_number(&digits, comp), vec![13, 45]); + + let comp = vec![&1, &1, &1, &1]; + assert_eq!(split_number(&digits, comp), vec![1, 3, 4, 5]); + + let comp = vec![&1, &2, &1]; + assert_eq!(split_number(&digits, comp), vec![1, 34, 5]); + } + + #[test] + fn test_is_s_number() { + assert!(is_s_number(81)); + assert!(is_s_number(6724)); + assert!(is_s_number(8281)); + assert!(is_s_number(9801)); + } + + #[test] + fn test_sum_of_s_numbers() { + assert_eq!(sum_of_s_numbers(1..=10000), 41333); + } + + #[test] + fn test_list_of_s_numbers() { + assert_eq!( + list_of_s_numbers(1..=100000), + vec![ + 81, 100, 1296, 2025, 3025, 6724, 8281, 9801, 10000, 55225, 88209 + ] + ); + } + + #[test] + fn test_s_numbers_up_to() { + assert_eq!( + list_of_s_numbers_up_to(100000), + list_of_s_numbers(1..=100000) + ); + assert_eq!(sum_of_s_numbers_up_to(100000), sum_of_s_numbers(1..=100000)); + } + + #[test] + fn test_list_of_s_numbers_of_digit() { + assert_eq!(list_of_s_numbers_of_digit(2), HashSet::from([81])); + assert_eq!(list_of_s_numbers_of_digit(3), HashSet::from([100])); + assert_eq!( + list_of_s_numbers_of_digit(4), + HashSet::from([1296, 2025, 3025, 6724, 8281, 9801]) + ); + } + + #[test] + #[ignore] + fn test_list_of_s_numbers_of_digit_large() { + assert_eq!( + list_of_s_numbers_of_digit(5), + HashSet::from([10000, 55225, 88209]) + ); + assert_eq!( + list_of_s_numbers_of_digit(6), + HashSet::from([ + 136161, 136900, 143641, 171396, 431649, 455625, 494209, 571536, 627264, 826281, + 842724, 893025, 929296, 980100, 982081, 998001 + ]) + ); + } + + #[test] + fn test_digit_to_num() { + assert_eq!(digit_to_num(&vec![1, 2, 3]), 123); + assert_eq!(digit_to_num(&vec![1, 2, 3, 4]), 1234); + } + + #[test] + fn test_check_s_number() { + let mut s_number = SNumber::new(&vec![1, 1]); + s_number.digits = vec![Some(8), Some(1)]; + assert_eq!(s_number.check(), Some(81)); + s_number.digits = vec![Some(1), Some(8)]; + assert_eq!(s_number.check(), None); + + let mut s_number = SNumber::new(&vec![1, 2, 1]); + s_number.digits = vec![Some(6), Some(7), Some(2), Some(4)]; + assert_eq!(s_number.check(), Some(6724)); + s_number.digits = vec![Some(4), Some(7), Some(2), Some(6)]; + assert_eq!(s_number.check(), None); + + let mut s_number = SNumber::new(&vec![2, 1, 1]); + s_number.digits = vec![Some(9), Some(8), Some(0), Some(1)]; + assert_eq!(s_number.check(), Some(9801)); + + s_number.digits = vec![Some(8), Some(2), Some(8), Some(1)]; + assert_eq!(s_number.check(), Some(8281)); + } + + #[test] + fn test_min_max_digit() { + let mut s_number = SNumber::new(&vec![1, 1]); + assert_eq!(s_number.min_digit(), vec![1, 0]); + assert_eq!(s_number.max_digit(), vec![9, 9]); + + s_number.digits[0] = Some(2); + assert_eq!(s_number.min_digit(), vec![2, 0]); + assert_eq!(s_number.max_digit(), vec![2, 9]); + + s_number.digits[1] = Some(2); + assert_eq!(s_number.min_digit(), vec![2, 2]); + assert_eq!(s_number.max_digit(), vec![2, 2]); + + let mut s_number = SNumber::new(&vec![2, 1, 1]); + assert_eq!(s_number.min_digit(), vec![1, 0, 0, 0]); + assert_eq!(s_number.max_digit(), vec![9, 9, 9, 9]); + + s_number.digits[1] = Some(2); + assert_eq!(s_number.min_digit(), vec![1, 2, 0, 0]); + assert_eq!(s_number.max_digit(), vec![9, 2, 9, 9]); + } + + #[test] + fn test_range_num() { + let mut s_number = SNumber::new(&vec![1, 1]); + assert_eq!(s_number.range_num(), (10, 99)); + + s_number.digits[0] = Some(2); + assert_eq!(s_number.range_num(), (20, 29)); + + s_number.digits[0] = None; + s_number.digits[1] = Some(2); + assert_eq!(s_number.range_num(), (12, 92)); + } + + #[test] + fn test_range_comp_sum() { + let mut s_number = SNumber::new(&vec![1, 1]); + assert_eq!(s_number.range_comp_sum(), (1, 18)); + + s_number.digits[0] = Some(2); + assert_eq!(s_number.range_comp_sum(), (2, 11)); + + s_number.digits[0] = None; + s_number.digits[1] = Some(2); + assert_eq!(s_number.range_comp_sum(), (3, 11)); + + let mut s_number = SNumber::new(&vec![2, 1]); + assert_eq!(s_number.range_comp_sum(), (10, 108)); + + s_number.digits[0] = Some(2); + assert_eq!(s_number.range_comp_sum(), (20, 38)); + + s_number.digits[0] = None; + s_number.digits[1] = Some(2); + assert_eq!(s_number.range_comp_sum(), (12, 101)); + + let mut s_number = SNumber::new(&vec![1, 2, 1]); + assert_eq!(s_number.range_comp_sum(), (1, 117)); + + s_number.digits[0] = Some(2); + assert_eq!(s_number.range_comp_sum(), (2, 110)); + + s_number.digits[0] = None; + s_number.digits[1] = Some(2); + assert_eq!(s_number.range_comp_sum(), (21, 47)); + } + + #[test] + fn test_dominant_digit() { + let mut s_number = SNumber::new(&vec![1, 1]); + assert_eq!(s_number.dominant_digit(), 0); + + s_number.digits[0] = Some(2); + assert_eq!(s_number.dominant_digit(), 1); + + s_number.digits[0] = None; + s_number.digits[1] = Some(2); + assert_eq!(s_number.dominant_digit(), 0); + + let mut s_number = SNumber::new(&vec![2, 3, 1]); + assert_eq!(s_number.dominant_digit(), 2); + + s_number.digits[2] = Some(2); + assert_eq!(s_number.dominant_digit(), 0); + + s_number.digits[0] = Some(2); + assert_eq!(s_number.dominant_digit(), 3); + } + + #[test] + fn test_has_common_range() { + let mut s_number = SNumber::new(&vec![1, 1]); + assert!(s_number.has_common_range()); + + for i in 1..=9 { + s_number.digits[0] = Some(i); + assert!(s_number.has_common_range()); + } + + let mut s_number = SNumber::new(&vec![1, 1, 1, 1]); + assert!(s_number.has_common_range()); + + for i in 1..=9 { + s_number.digits[0] = Some(i); + assert!(!s_number.has_common_range()); + } + } + + #[test] + fn test_find_solution() { + let mut s_number = SNumber::new(&vec![1, 1]); + assert_eq!(s_number.find_solution(), HashSet::from([81])); + + let mut s_number = SNumber::new(&vec![2, 1]); + assert_eq!(s_number.find_solution(), HashSet::from([100])); + + let mut s_number = SNumber::new(&vec![1, 2, 1]); + assert_eq!(s_number.find_solution(), HashSet::from([1296, 6724])); + + let mut s_number = SNumber::new(&vec![2, 1, 1]); + assert_eq!(s_number.find_solution(), HashSet::from([8281, 9801])); + + let mut s_number = SNumber::new(&vec![2, 2]); + assert_eq!(s_number.find_solution(), HashSet::from([2025, 3025, 9801])); + + let mut s_number = SNumber::new(&vec![3, 2]); + assert_eq!(s_number.find_solution(), HashSet::from([10000])); + } + + #[test] + fn test_list_of_s_numbers_final() { + assert_eq!( + list_of_s_numbers_final(6), + HashSet::from([ + 81, 100, 1296, 2025, 3025, 6724, 8281, 9801, 10000, 55225, 88209, 136161, 136900, + 143641, 171396, 431649, 455625, 494209, 571536, 627264, 826281, 842724, 893025, + 929296, 980100, 982081, 998001 + ]) + ); + + assert_eq!(list_of_s_numbers_final(4).iter().sum::(), 31333); + // assert_eq!(list_of_s_numbers_final(12).iter().sum::(), 41333); + } + + #[test] + fn test_sum_s_number_answer() { + assert_eq!(sum_of_s_numbers_answer(10000), 41333); + assert_eq!(sum_of_s_numbers_answer(1_000_000_000_000), 128088830547982); + } +} diff --git a/src/utils/combinatoric.rs b/src/utils/combinatoric.rs index 985fe53..c9d8147 100644 --- a/src/utils/combinatoric.rs +++ b/src/utils/combinatoric.rs @@ -1,3 +1,6 @@ +use itertools::Itertools; +use std::collections::HashSet; + pub fn factorial(n: usize) -> usize { assert!( n <= 20, @@ -16,3 +19,195 @@ pub fn combination(n: usize, k: usize) -> usize { pub fn combination_with_repetition(n: usize, k: usize) -> usize { combination(n + k - 1, k) } + +pub fn composition_of_length(n: usize, length: usize) -> Vec> { + fn dfs( + n: usize, + length: usize, + idx: usize, + current: &mut Vec, + result: &mut Vec>, + ) { + let remain = n - current.iter().sum::(); + let value_limit = if idx == 0 { n - 1 } else { current[idx - 1] }; + if remain == 0 && idx == length { + result.push(current.clone()); + return; + } else if remain > (length - idx) * value_limit || idx == length { + return; + } + + let max_value = remain.min(value_limit); + for i in (1..=max_value).rev() { + current[idx] = i; + dfs(n, length, idx + 1, current, result); + } + current[idx] = 0; + } + + let mut result = Vec::new(); + let mut current = vec![0; length]; + dfs(n, length, 0, &mut current, &mut result); + result +} + +pub fn composition(n: usize) -> Vec> { + let mut result = Vec::new(); + for length in 2..=n { + result.extend(composition_of_length(n, length)); + } + result +} + +pub fn permuted_composition(n: usize) -> HashSet> { + let mut visited = HashSet::new(); + for length in 2..=n { + for comp in composition_of_length(n, length) { + for perm in comp.iter().permutations(length) { + visited.insert(perm.iter().map(|d| **d).collect::>()); + } + } + } + visited +} + +pub fn composition_of_length_with_min_max( + n: usize, + length: usize, + minmax: usize, +) -> Vec> { + fn dfs( + n: usize, + length: usize, + idx: usize, + current: &mut Vec, + result: &mut Vec>, + ) { + let remain = n - current.iter().sum::(); + let value_limit = if idx == 0 { n - 1 } else { current[idx - 1] }; + if remain == 0 && idx == length { + result.push(current.clone()); + return; + } else if remain > (length - idx) * value_limit || idx == length { + return; + } + + let max_value = remain.min(value_limit); + for i in (1..=max_value).rev() { + current[idx] = i; + dfs(n, length, idx + 1, current, result); + } + current[idx] = 0; + } + + let mut result = Vec::new(); + let mut current = vec![0; length]; + for i in (minmax..n).rev() { + current[0] = i; + dfs(n, length, 1, &mut current, &mut result); + } + result +} + +pub fn composition_with_min_max(n: usize, minmax: usize) -> Vec> { + let mut result = Vec::new(); + for length in 2..=(n - minmax + 1) { + result.extend(composition_of_length_with_min_max(n, length, minmax)); + } + result +} + +pub fn permuted_composition_with_min_max(n: usize, minmax: usize) -> HashSet> { + let mut visited = HashSet::new(); + for length in 2..=(n - minmax + 1) { + for comp in composition_of_length_with_min_max(n, length, minmax) { + for perm in comp.iter().permutations(length) { + visited.insert(perm.iter().map(|d| **d).collect::>()); + } + } + } + visited +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_composition_of_length() { + let result = composition_of_length(10, 2); + assert_eq!( + result, + vec![vec![9, 1], vec![8, 2], vec![7, 3], vec![6, 4], vec![5, 5]] + ); + + let result = composition_of_length(8, 3); + assert_eq!( + result, + vec![ + vec![6, 1, 1], + vec![5, 2, 1], + vec![4, 3, 1], + vec![4, 2, 2], + vec![3, 3, 2] + ] + ); + } + + #[test] + fn test_composition() { + let result = composition(5); + assert_eq!( + result, + vec![ + vec![4, 1], + vec![3, 2], + vec![3, 1, 1], + vec![2, 2, 1], + vec![2, 1, 1, 1], + vec![1, 1, 1, 1, 1] + ] + ); + } + + #[test] + fn test_permuted_composition() { + let result = permuted_composition(3); + assert_eq!( + result, + HashSet::from([vec![1, 2], vec![2, 1], vec![1, 1, 1]]) + ); + + let result = permuted_composition(4); + assert_eq!( + result, + HashSet::from([ + vec![1, 3], + vec![3, 1], + vec![2, 2], + vec![1, 1, 2], + vec![1, 2, 1], + vec![2, 1, 1], + vec![1, 1, 1, 1] + ]) + ) + } + + #[test] + fn test_composition_of_length_with_min_max() { + let result = composition_of_length_with_min_max(2, 2, 1); + assert_eq!(result, vec![vec![1, 1]]); + + let result = composition_of_length_with_min_max(8, 2, 6); + assert_eq!(result, vec![vec![7, 1], vec![6, 2]]); + + let result = composition_of_length_with_min_max(8, 3, 6); + assert_eq!(result, vec![vec![6, 1, 1]]); + } + + #[test] + fn test_composition_with_min_max() { + let result = composition_with_min_max(8, 6); + assert_eq!(result, vec![vec![7, 1], vec![6, 2], vec![6, 1, 1]]); + } +}