Number Theory in Programming
Number Theory in Programming
Gyumin Roh
August 17, 2016
How do we prove that this algorithm is O(log ab)? Well, let’s suppose that we started
with (a, b). Then, we go to (b, r), where r is defined similarly as above. It can be proved
1
that br < ab. Therefore, the product of two numbers in the function decreases by half
2
every time. Done!
Here’s a challenge. Can we find the numbers x, y such that ux + vy = gcd(u, v)?
There exists infinitely many pairs - this is Bezout’s Lemma. The algorithm to generate
such pairs is called Extended Euclidean Algorithm.
1
Gyumin Roh (August 17, 2016) Number Theory in Competitive Programming
It’s not checked, so it is a prime number. Now check all numbers that are multiple of 3,
except 3. Now move on to 4. We see that this is checked - this is a multiple of 2! So 4 is
not a prime. We continue doing this.
Here’s the implementation.
#include <stdio.h>
int primechk[21000];
void preprocess(void)
{
int i, j;
for(i=2 ; i<=20000 ; i++)
{
primechk[i]=1;
}
for(i=2 ; i<=20000 ; i++)
{
if(primechk[i]==1)
{
for(j=2 ; i*j<=20000 ; j++)
{
primechk[i*j]=0;
}
}
}
}
int main(void)
{
preprocess();
int i, cnt=0;
for(i=2 ; i<=20000 ; i++)
{
if(primechk[i]==1)
{
cnt++;
printf("%d is the %dth prime!\n",i,cnt);
}
}
}
Okay, so what is the time complexity? To get all primes in the interval [1, n], the TC of
this algorithm is O(n log log n). Very X
very fast. To prove this, notice that the number of
n
iterations are something like O(n) + where p is a prime.
p
p≤n
X1
Well, Merten’s Second Theorem states that = log log n + O(1) (natural logarithm,
p
p≤n
by the way) so this prove the TC.
Now we know how to generate prime numbers fast. How about primality testing?
√
Naive solutions first. Given an integer n, we can check numbers up to n to find if a
number divides n. If there is such a number, n is composite. If not, n is a prime. This
√
gives the solution in O( n).
Here’s a "solution" in O(c ln n) using the fast exponentiation we will talk about in
the next section. It’s called Miller-Rabin Primality Test. I’ll introduce the deterministic
2
Gyumin Roh (August 17, 2016) Number Theory in Competitive Programming
version. We probably (hopefully) won’t see stuff like n > 4 · 109 in contests.
Here’s the sketch of the algorithm. Choose some set of a. We will run the algorithm
with different as, and the more as we run this algorithm with, the more accurate this
primality test is going to be.
Decompose n − 1 as 2s · d. Then check if the following holds.
rd
ad 6≡ 1 (mod n) and a2 6≡ −1 (mod n) for all r ∈ [0, s − 1]
int main(void)
{
preprocess();
int n;
scanf("%d",&n);
while(n!=1)
{
printf("%d\n",fprime[n]);
n=n/fprime[n];
}
3
Gyumin Roh (August 17, 2016) Number Theory in Competitive Programming
}
√
If not, we can just check through all primes less than n and divide by those primes
until we can’t. If all the primes multiply up to n, we are done. If not, there is exactly one
√
prime more than n that divides n.
Another algorithm involving prime factorization is Pollard’s rho algorithm - since the
pseudocode is simple, I’ll leave you the wikipedia link: Pollard’s rho algorithm
4
Gyumin Roh (August 17, 2016) Number Theory in Competitive Programming
Also, you can get the modular inverse of all numbers in [1, n] in O(n). The code for
this is shown below. The proof for the correctness is left to the reader (not difficult)
#include <iostream>
typedef long long ll;
using namespace std;
int inv[111111], n;
ll mod=1e9+7;
int main(void)
{
cin>>n;
int i;
inv[1]=1;
for(i=2 ; i<=n ; i++)
{
inv[i]=((mod-mod/i)*inv[mod%i])%mod;
cout<<inv[i]<<endl;
}
}
n
We can also calculate in modulo p (p is a prime) very fast using Lucas’ Theorem.
m n
n n0 bpc
Lucas’ Theorem basically states that ≡ · m , where n0 is n modulo p
m m0 bpc
and m0 is m modulo p.
This is very efficient when p is small and n, m is huge. We can precalculate the factorials
and inverse of factorials modulo p by using the above code, and solve each queries in
O(logp max(n, m)).
Also, we can use Chinese Remainder Theorem to solve a system of modular equations.
Let us solve x ≡ ri (mod mi ), where mi are pairwise coprime. (If they are not coprime,
break them into prime powers, and if some are contradictory, there are no solutions.)
n
Y M
The CRT itself gives an algorithm to get our answer. Set M = mi , and ui = .
mi
i=1
X n
Also, set si as the modular inverse of ui in modulo mi . Then our answer is ri si ui
i=1
(mod M ).
We learned how to calculate modular inverse in logarithm time above. So the time
complexity is O(n log M AX).
long long int r[111111]; // remainders
long long int m[111111]; // modulars
long long int M=1; // product
int n; // number of equation
int res(void)
{
int i;
for(i=1 ; i<=n ; i++)
{
M=M*m[i];
}
long long int ret=0;
for(i=1 ; i<=n ; i++)
{
5
Gyumin Roh (August 17, 2016) Number Theory in Competitive Programming
ret+=r[i]*inv(M/m[i],m[i])*(M/m[i]);
ret=ret%M;
}
return ret;
}
φ(n) is the number ofpositive integers no more than n which is coprime with n. Formula is
Y 1 X
φ(n) = n 1− . Proof is Inclusion-Exclusion. Also, we have the formula φ(d) =
p
p|n d|n
n.
Of course, for the calculation of Euler Phi numbers, we can tweak the Eratosthenes’s
Sieve algorithm a little bit.
void preprocess(void)
{
int i, j;
eulerphi[1]=1;
for(i=2 ; i<=122000 ; i++)
{
eulerphi[i]=i;
primechk[i]=1;
}
for(i=2 ; i<=122000 ; i++)
{
if(primechk[i]==1)
{
eulerphi[i]-=eulerphi[i]/i;
for(j=2 ; i*j<=122000 ; j++)
{
primechk[i*j]=0;
eulerphi[i*j]-=eulerphi[i*j]/i;
}
}
}
}
µ(n)
Xalso has a lot of interesting properties that make µ(n) so important.
µ(d) = 0 for all n > 1, and µ(n) is multiplicative. (A function f is multiplicative if
d|n
f (mn) = f (m)f (n) for all (m, n) = 1.)
6
Gyumin Roh (August 17, 2016) Number Theory in Competitive Programming
How do we calculate µ(n) fast? Again, we can tweak the Eratosthenes’s Sieve a little
bit.
void preprocess(void)
{
int i, j;
for(i=1 ; i<=111100 ; i++)
{
mu[i]=1;
primechk[i]=1;
}
primechk[1]=0;
for(i=2 ; i<=111100 ; i++)
{
if(primechk[i]==1)
{
mu[i]=-mu[i];
for(j=2 ; i*j<=111100 ; j++)
{
primechk[i*j]=0;
if(j%i==0)
{
mu[i*j]=0;
}
else
{
mu[i*j]=-mu[i*j];
}
}
}
}
}
7
Gyumin Roh (August 17, 2016) Number Theory in Competitive Programming
Chứng minh.
X n X X
µ(d)g = µ(d) f (c)
d
d|n d|n c| n
d
XX
= µ(d)f (c)
d|n c| n
d
XX
= f (c)µ(d)
c|n d| nc
X X
= f (c) µ(d) = f (n)
c|n d| nc
X
using µ(d) = 0 for all n > 1 and µ(1) = 1.
d|n
Denote 1 as the constant function, f (n) = 1. (n) is a function which satisfies (1) = 1
and (n) = 0 for n 6= 1. Id(n) is the identity function, Id(n) = n
The mobius inversion formula and the basic property of mobius functions give 1 ∗ µ = ,
and g = f ∗ 1 ⇐⇒ f = g ∗ µ
Okay. So what? How can we use this formula to solve problems?
We can, change our "sum" or our answer using mobius inversion and mobius functions
to calculate them fast.
I will give 3 examples which changes the desired sum by using number theory (like
mobius function) to calculate them fast.
Solution.
n n X nφ( n )
X X i d n nX
lcm(i, n) = n =n+n = + dφ(d)
gcd(i, n) 2d 2 2
i=1 i=1 d|n,d6=n d|n
8
Gyumin Roh (August 17, 2016) Number Theory in Competitive Programming
}
for(int i=2 ; i<=1000000 ; i++)
{
if(phi[i]==i)
{
for(int j=1; i*j<=n; j++)
{
phi[i*j]-=phi[i*j]/i;
}
}
}
for(int i=1 ; i<=1000000 ; i++)
{
for(int j=1; i*j<=1000000 ; j++)
{
res[i*j]+=i*phi[i];
}
}
}
int main(void)
{
preprocess();
int tc;
cin>>tc;
while (tc--)
{
ll n;
scanf("%lld",&n);
ll ans=res[n]+1;
ans=(ans*n)/2;
printf("%lld\n",ans);
}
return 0;
}
9
Gyumin Roh (August 17, 2016) Number Theory in Competitive Programming
#include <vector>
#include <string.h>
#include <string>
#include <stack>
#include <queue>
#include <iostream>
#include <assert.h>
#include <math.h>
using namespace std;
typedef long long int ll;
ll primechk[111111];
ll mu[111111];
ll divcnt[111111];
ll cnt[111111];
ll com[333][333];
ll ans, mod=1e7+3;
int n;
void input(void)
{
int i, j, x;
cin>>n;
for(i=1 ; i<=n ; i++)
{
scanf("%d",&x);
cnt[x]++;
}
}
void divpproc(void)
{
int i, j, x;
for(i=1 ; i<=110000 ; i++)
{
for(j=1 ; i*j<=110000 ; j++)
{
divcnt[i]+=cnt[i*j];
}
}
}
void compproc(void)
{
com[0][0]=1;
int i, j;
for(i=1 ; i<=320 ; i++)
{
com[i][0]=1;
com[i][i]=1;
}
for(i=2 ; i<=320 ; i++)
{
for(j=1 ; j<=i-1 ; j++)
{
com[i][j]=(com[i-1][j-1]+com[i-1][j]);
}
}
10
Gyumin Roh (August 17, 2016) Number Theory in Competitive Programming
void preprocess(void)
{
int i, j;
for(i=1 ; i<=111100 ; i++)
{
mu[i]=1;
primechk[i]=1;
}
primechk[1]=0;
for(i=2 ; i<=111100 ; i++)
{
if(primechk[i]==1)
{
mu[i]=-mu[i];
for(j=2 ; i*j<=111100 ; j++)
{
primechk[i*j]=0;
if(j%i==0)
{
mu[i*j]=0;
}
else
{
mu[i*j]=-mu[i*j];
}
}
}
}
}
void calc(void)
{
int i, j;
for(i=1 ; i<=105000 ; i++)
{
ans=ans+mu[i]*com[divcnt[i]][3];
}
}
int main(void)
{
preprocess();
input();
divpproc();
compproc();
calc();
cout<<ans;
}
11
Gyumin Roh (August 17, 2016) Number Theory in Competitive Programming
X
The main problem is calculating the sum of all gcds. Using φ(d) = n, we can change
d|n
the sum. X
The sum is pretty much g · number of tuples that have gcd g.
g
X
Now change g to φ(d). Therefore, we can change the sum to
d|g
X
φ(d) · number of tuples that have gcd a multiple d.
d
But this is way easier to calculate! For the gcd to be a multiple of d, we can just use
k−1
Y
number of multiple of d in the interval [Ai , Bi ]. Done!
i=0
In the more harder problems, the result of mobius inversion gets more complex, and
j nin
k
some problems we also have to keep track of some prefix sums and use the fact that
√ i
takes O( n) values. But that is for later.
Thanks for reading this. Hope this helped.
12