modular_binomial_fast.pl 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301
  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 (+some optimizations):
  6. # https://github.com/hellman/libnum/blob/master/libnum/modular.py
  7. # Translated by: Trizen
  8. # Date: 29 September 2017
  9. # Edit: 28 April 2022
  10. # https://github.com/trizen
  11. use 5.020;
  12. use strict;
  13. use warnings;
  14. use ntheory qw(:all);
  15. use experimental qw(signatures);
  16. sub modular_binomial ($n, $k, $m) {
  17. if ($m == 0) {
  18. return undef;
  19. }
  20. if ($m == 1) {
  21. return 0;
  22. }
  23. if ($k < 0) {
  24. $k = subint($n, $k);
  25. }
  26. if ($k < 0) {
  27. return 0;
  28. }
  29. if ($n < 0) {
  30. return modint(mulint(powint(-1, $k), __SUB__->(subint($k, $n) - 1, $k, $m)), $m);
  31. }
  32. if ($k > $n) {
  33. return 0;
  34. }
  35. if ($k == 0 or $k == $n) {
  36. return modint(1, $m);
  37. }
  38. if ($k == 1 or $k == subint($n, 1)) {
  39. return modint($n, $m);
  40. }
  41. my @congruences;
  42. foreach my $pair (factor_exp(absint($m))) {
  43. my ($p, $e) = @$pair;
  44. if ($e == 1) {
  45. push @congruences, [lucas_theorem($n, $k, $p), $p];
  46. }
  47. else {
  48. push @congruences, [modular_binomial_prime_power($n, $k, $p, $e), powint($p, $e)];
  49. }
  50. }
  51. modint(chinese(@congruences), $m);
  52. }
  53. #<<<
  54. #~ sub factorial_prime_pow ($n, $p) {
  55. #~ divint(subint($n, sumdigits($n, $p)), subint($p, 1));
  56. #~ }
  57. #>>>
  58. sub factorial_prime_pow ($n, $p) {
  59. my $count = 0;
  60. my $ppow = $p;
  61. while ($ppow <= $n) {
  62. $count = addint($count, divint($n, $ppow));
  63. $ppow = mulint($ppow, $p);
  64. }
  65. return $count;
  66. }
  67. sub binomial_prime_pow ($n, $k, $p) {
  68. #<<<
  69. factorial_prime_pow($n, $p)
  70. - factorial_prime_pow($k, $p)
  71. - factorial_prime_pow(subint($n, $k), $p);
  72. #>>>
  73. }
  74. sub factorial_without_prime ($n, $p, $pk) {
  75. return 1 if ($n <= 1);
  76. if ($p > $n) {
  77. return factorialmod($n, $pk);
  78. }
  79. my $r = 1;
  80. my $t = 0;
  81. foreach my $v (1 .. $n) {
  82. if (++$t == $p) {
  83. $t = 0;
  84. }
  85. else {
  86. $r = mulmod($r, $v, $pk);
  87. }
  88. }
  89. return $r;
  90. }
  91. sub lucas_theorem ($n, $k, $p) { # p is prime
  92. my $r = 1;
  93. while ($k) {
  94. my $np = modint($n, $p);
  95. my $kp = modint($k, $p);
  96. if ($kp > $np) { return 0 }
  97. my $rp = subint($np, $kp);
  98. my $x = factorialmod($np, $p);
  99. my $y = factorialmod($kp, $p);
  100. my $z = factorialmod($rp, $p);
  101. $y = mulmod($y, $z, $p);
  102. $x = divmod($x, $y, $p);
  103. $r = mulmod($r, $x, $p);
  104. $n = divint($n, $p);
  105. $k = divint($k, $p);
  106. }
  107. return $r;
  108. }
  109. sub binomial_non_prime_part ($n, $k, $p, $e) {
  110. my $pe = powint($p, $e);
  111. my $r = subint($n, $k);
  112. my $acc = 1;
  113. my @fact_pe = (1);
  114. if ($pe < ~0 and $p < $n) {
  115. my $count = 0;
  116. foreach my $x (1 .. vecmin(1e4, $pe - 1)) {
  117. if (++$count == $p) {
  118. $count = 0;
  119. }
  120. else {
  121. $acc = mulmod($acc, $x, $pe);
  122. }
  123. push @fact_pe, $acc;
  124. }
  125. }
  126. my $top = 1;
  127. my $bottom = 1;
  128. my $is_negative = 0;
  129. my $digits = 0;
  130. while ($n) {
  131. if ($digits >= $e) {
  132. $is_negative ^= modint($n, 2);
  133. $is_negative ^= modint($r, 2);
  134. $is_negative ^= modint($k, 2);
  135. }
  136. my $np = modint($n, $pe);
  137. my $rp = modint($r, $pe);
  138. my $kp = modint($k, $pe);
  139. #<<<
  140. $top = mulmod($top, ($fact_pe[$np] // factorial_without_prime($np, $p, $pe)), $pe);
  141. $bottom = mulmod($bottom, ($fact_pe[$rp] // factorial_without_prime($rp, $p, $pe)), $pe);
  142. $bottom = mulmod($bottom, ($fact_pe[$kp] // factorial_without_prime($kp, $p, $pe)), $pe);
  143. #>>>
  144. $n = divint($n, $p);
  145. $r = divint($r, $p);
  146. $k = divint($k, $p);
  147. ++$digits;
  148. }
  149. my $res = divmod($top, $bottom, $pe);
  150. if ($is_negative and ($p != 2 or $e < 3)) {
  151. $res = subint($pe, $res);
  152. }
  153. return $res;
  154. }
  155. sub modular_binomial_prime_power ($n, $k, $p, $e) {
  156. my $pow = binomial_prime_pow($n, $k, $p);
  157. if ($pow >= $e) {
  158. return 0;
  159. }
  160. my $er = $e - $pow;
  161. my $r = modint(binomial_non_prime_part($n, $k, $p, $er), powint($p, $er));
  162. my $pe = powint($p, $e);
  163. return mulmod(powmod($p, $pow, $pe), $r, $pe);
  164. }
  165. use Test::More tests => 44;
  166. is(modular_binomial(10, 2, 43), 2);
  167. is(modular_binomial(10, 8, 43), 2);
  168. is(modular_binomial(10, 2, 24), 21);
  169. is(modular_binomial(10, 8, 24), 21);
  170. is(modular_binomial(100, 42, -127), binomial(100, 42) % -127);
  171. is(modular_binomial(12, 5, 100000), 792);
  172. is(modular_binomial(16, 4, 100000), 1820);
  173. is(modular_binomial(100, 50, 139), 71);
  174. is(modular_binomial(1000, 10, 1243), 848);
  175. is(modular_binomial(124, 42, 1234567), 395154);
  176. is(modular_binomial(1e9, 1e4, 1234567), 833120);
  177. is(modular_binomial(1e10, 1e5, 1234567), 589372);
  178. is(modular_binomial(1e10, 1e5, 4233330243), 3403056024);
  179. is(modular_binomial(-1e10, 1e5, 4233330243), 2865877173);
  180. is(modular_binomial(1e10, 1e4, factorial(13)), 1845043200);
  181. is(modular_binomial(1e10, 1e5, factorial(13)), 1556755200);
  182. is(modular_binomial(1e10, 1e6, factorial(13)), 5748019200);
  183. is(modular_binomial(-1e10, 1e4, factorial(13)), 4151347200);
  184. is(modular_binomial(-1e10, 1e5, factorial(13)), 1037836800);
  185. is(modular_binomial(-1e10, 1e6, factorial(13)), 2075673600);
  186. is(modular_binomial(3, 1, 9), binomial(3, 1) % 9);
  187. is(modular_binomial(4, 1, 16), binomial(4, 1) % 16);
  188. is(modular_binomial(1e9, 1e5, 43 * 97 * 503), 585492);
  189. is(modular_binomial(1e9, 1e6, 5041689707), 15262431);
  190. is(modular_binomial(1e7, 1e5, 43**2 * 97**3 * 13**4), 1778017500428);
  191. is(modular_binomial(1e7, 1e5, 42**2 * 97**3 * 13**4), 10015143223176);
  192. is(modular_binomial(1e9, 1e5, 12345678910), 4517333900);
  193. is(modular_binomial(1e9, 1e6, 13**2 * 5**6), 2598375);
  194. is(modular_binomial(1e10, 1e5, 1234567), 589372);
  195. is(modular_binomial(1e5, 1e3, 43), binomial(1e5, 1e3) % 43);
  196. is(modular_binomial(1e5, 1e3, 43 * 97), binomial(1e5, 1e3) % (43 * 97));
  197. is(modular_binomial(1e5, 1e3, 43 * 97 * 43), binomial(1e5, 1e3) % (43 * 97 * 43));
  198. is(modular_binomial(1e5, 1e3, 43 * 97 * (5**5)), binomial(1e5, 1e3) % (43 * 97 * (5**5)));
  199. is(modular_binomial(1e5, 1e3, next_prime(1e4)**2), binomial(1e5, 1e3) % next_prime(1e4)**2);
  200. is(modular_binomial(1e5, 1e3, next_prime(1e4)), binomial(1e5, 1e3) % next_prime(1e4));
  201. is(modular_binomial(1e6, 1e3, next_prime(1e5)), binomial(1e6, 1e3) % next_prime(1e5));
  202. is(modular_binomial(1e6, 1e3, next_prime(1e7)), binomial(1e6, 1e3) % next_prime(1e7));
  203. is(modular_binomial(1234567, 1e3, factorial(20)), binomial(1234567, 1e3) % factorial(20));
  204. is(modular_binomial(1234567, 1e4, factorial(20)), binomial(1234567, 1e4) % factorial(20));
  205. is(modular_binomial(1e6, 1e3, powint(2, 128) + 1), binomial(1e6, 1e3) % (powint(2, 128) + 1));
  206. is(modular_binomial(1e6, 1e3, powint(2, 128) - 1), binomial(1e6, 1e3) % (powint(2, 128) - 1));
  207. is(modular_binomial(1e6, 1e4, (powint(2, 128) + 1)**2), binomial(1e6, 1e4) % ((powint(2, 128) + 1)**2));
  208. is(modular_binomial(1e6, 1e4, (powint(2, 128) - 1)**2), binomial(1e6, 1e4) % ((powint(2, 128) - 1)**2));
  209. is(modular_binomial(-10, -9, -10), binomial(-10, -9) % -10);
  210. say("binomial(10^10, 10^5) mod 13! = ", modular_binomial(1e10, 1e5, factorial(13)));
  211. say modular_binomial(12, 5, 100000); #=> 792
  212. say modular_binomial(16, 4, 100000); #=> 1820
  213. say modular_binomial(100, 50, 139); #=> 71
  214. say modular_binomial(1000, 10, 1243); #=> 848
  215. say modular_binomial(124, 42, 1234567); #=> 395154
  216. say modular_binomial(1e9, 1e4, 1234567); #=> 833120
  217. say modular_binomial(1e10, 1e5, 1234567); #=> 589372
  218. __END__
  219. my $upto = 10;
  220. foreach my $n (-$upto .. $upto) {
  221. foreach my $k (-$upto .. $upto) {
  222. foreach my $m (-$upto .. $upto) {
  223. next if ($m == 0);
  224. say "Testing: binomial($n, $k, $m)";
  225. is(modular_binomial($n, $k, $m), binomial($n, $k) % $m);
  226. }
  227. }
  228. }