lucas_theorem.pl 2.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103
  1. #!/usr/bin/perl
  2. # Daniel "Trizen" Șuteu
  3. # Date 04 September 2020
  4. # https://github.com/trizen
  5. # Simple implementation of Lucas's theorem, for computing binomial(n,k) mod p, for some prime p.
  6. # See also:
  7. # https://en.wikipedia.org/wiki/Lucas%27s_theorem
  8. use 5.020;
  9. use strict;
  10. use warnings;
  11. use experimental qw(signatures);
  12. use ntheory qw(:all);
  13. sub factorial_valuation ($n, $p) {
  14. ($n - vecsum(todigits($n, $p))) / ($p - 1);
  15. }
  16. sub modular_binomial ($n, $k, $m) { # fast for small n
  17. my $j = $n - $k;
  18. my $prod = 1;
  19. forprimes {
  20. my $p = factorial_valuation($n, $_);
  21. if ($_ <= $k) {
  22. $p -= factorial_valuation($k, $_);
  23. }
  24. if ($_ <= $j) {
  25. $p -= factorial_valuation($j, $_);
  26. }
  27. if ($p > 0) {
  28. $prod *= ($p == 1) ? ($_ % $m) : powmod($_, $p, $m);
  29. $prod %= $m;
  30. }
  31. } $n;
  32. return $prod;
  33. }
  34. sub lucas_theorem ($n, $k, $p) {
  35. if ($n < $k) {
  36. return 0;
  37. }
  38. my $res = 1;
  39. while ($k > 0) {
  40. my ($Nr, $Kr) = ($n % $p, $k % $p);
  41. if ($Nr < $Kr) {
  42. return 0;
  43. }
  44. ($n, $k) = (divint($n, $p), divint($k, $p));
  45. $res = mulmod($res, modular_binomial($Nr, $Kr, $p), $p);
  46. }
  47. return $res;
  48. }
  49. sub lucas_theorem_alt ($n, $k, $p) { # alternative implementation
  50. if ($n < $k) {
  51. return 0;
  52. }
  53. my @Nd = reverse todigits($n, $p);
  54. my @Kd = reverse todigits($k, $p);
  55. my $res = 1;
  56. foreach my $i (0 .. $#Kd) {
  57. my $Nr = $Nd[$i];
  58. my $Kr = $Kd[$i];
  59. if ($Nr < $Kr) {
  60. return 0;
  61. }
  62. $res = mulmod($res, modular_binomial($Nr, $Kr, $p), $p);
  63. }
  64. return $res;
  65. }
  66. say lucas_theorem(1e10, 1e5, 1009); #=> 559
  67. say lucas_theorem(powint(10, 18), powint(10, 9), 2957); #=> 2049
  68. say '';
  69. say lucas_theorem_alt(1e10, 1e5, 1009); #=> 559
  70. say lucas_theorem_alt(powint(10, 18), powint(10, 9), 2957); #=> 2049