365 A huge binomial coefficient.pl 2.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138
  1. #!/usr/bin/perl
  2. # Efficient algorithm for computing `binomial(n, k) mod m`, based on the factorization of `m`.
  3. # Algorithm by Andrew Granville:
  4. # https://www.scribd.com/document/344759427/BinCoeff-pdf
  5. # Algorithm translated from:
  6. # https://github.com/hellman/libnum/blob/master/libnum/modular.py
  7. # Translated by: Daniel "Trizen" Șuteu
  8. # Date: 29 September 2017
  9. # https://github.com/trizen
  10. # https://projecteuler.net/problem=365
  11. # Runtime: ~1 hour.
  12. # (see version 2 for a faster solution)
  13. use 5.020;
  14. use strict;
  15. use integer;
  16. use warnings;
  17. use experimental qw(signatures);
  18. use ntheory qw(chinese invmod mulmod primes powmod);
  19. sub factorial_prime_pow ($n, $p) {
  20. my $count = 0;
  21. my $ppow = $p;
  22. while ($ppow <= $n) {
  23. $count += $n / $ppow;
  24. $ppow *= $p;
  25. }
  26. return $count;
  27. }
  28. sub binomial_prime_pow ($n, $k, $p) {
  29. #<<<
  30. factorial_prime_pow($n, $p)
  31. - factorial_prime_pow($k, $p)
  32. - factorial_prime_pow($n - $k, $p);
  33. #>>>
  34. }
  35. sub binomial_non_prime_part ($n, $k, $p, $e) {
  36. my $pe = $p**$e;
  37. my $r = $n - $k;
  38. my $acc = 1;
  39. my @fact_pe = (1);
  40. foreach my $x (1 .. $pe - 1) {
  41. if ($x % $p == 0) {
  42. $x = 1;
  43. }
  44. $acc = ($acc * $x) % $pe;
  45. push @fact_pe, $acc;
  46. }
  47. my $top = 1;
  48. my $bottom = 1;
  49. my $is_negative = 0;
  50. my $digits = 0;
  51. while ($n) {
  52. if ($acc != 1 and $digits >= $e) {
  53. $is_negative ^= $n & 1;
  54. $is_negative ^= $r & 1;
  55. $is_negative ^= $k & 1;
  56. }
  57. #<<<
  58. $top = ($top * $fact_pe[$n % $pe]) % $pe;
  59. $bottom = ($bottom * $fact_pe[$r % $pe]) % $pe;
  60. $bottom = ($bottom * $fact_pe[$k % $pe]) % $pe;
  61. #>>>
  62. $n = $n / $p;
  63. $r = $r / $p;
  64. $k = $k / $p;
  65. ++$digits;
  66. }
  67. my $res = ($top * invmod($bottom, $pe)) % $pe;
  68. if ($is_negative and ($p != 2 or $e < 3)) {
  69. $res = $pe - $res;
  70. }
  71. return $res;
  72. }
  73. sub modular_binomial_prime_power ($n, $k, $p, $e) {
  74. my $pow = binomial_prime_pow($n, $k, $p);
  75. if ($pow >= $e) {
  76. return 0;
  77. }
  78. my $modpow = $e - $pow;
  79. my $r = binomial_non_prime_part($n, $k, $p, $modpow) % $p**$modpow;
  80. if ($pow == 0) {
  81. return ($r % $p**$e);
  82. }
  83. return mulmod(powmod($p, $pow, $p**$e), $r, $p**$e);
  84. }
  85. my @primes = @{primes(1000, 5000)};
  86. my $end = $#primes;
  87. my $N = 1000000000000000000;
  88. my $K = 1000000000;
  89. my $sum = 0;
  90. foreach my $i (0 .. $end - 2) {
  91. my $first = [modular_binomial_prime_power($N, $K, $primes[$i], 1), $primes[$i]];
  92. foreach my $j ($i + 1 .. $end - 1) {
  93. my $second = [modular_binomial_prime_power($N, $K, $primes[$j], 1), $primes[$j]];
  94. foreach my $k ($j + 1 .. $end) {
  95. my $third = [modular_binomial_prime_power($N, $K, $primes[$k], 1), $primes[$k]];
  96. $sum += chinese($first, $second, $third);
  97. }
  98. }
  99. say "$i -> $sum";
  100. }
  101. say $sum;