Student. CSE

Math Algorithm 정리

math Algorithm 정리

목차

제목 날짜
소수 (prime number) 24.09.07
유클리드 알고리즘 (Euclidean Algorithm) 24.09.07
빠른 거듭제곱 연산(exponentiation-by-squaring) 24.09.07
행렬에서의-연산 (caculation-by-matrix) 24.09.07
피보나치 수열 24.09.07
모듈러 연산(Modular Arthimetic) 24.09.08
진법변환(base conversion 24.09.08

소수 (prime number)

소수판정 - 밀러라빈 알고리즘

소수를 판별하려면 2부터 $sqrt(n)$까지의 모든 수에 대해서 $n$을 나누어 떨어지는 수가 있는지를 확인 하면 되고, 그런 수가 없다면 해당 수를 소수라고 판별할 수 있다.

하지만 이는 $n$이 작을때 사용하는 것이고, 만약 $n$이 크다면, 밀러-라빈 알고리즘을 사용할 수 있다. 밀러라빈 알고리즘은 확률 알고리즘으로, 소수의 성질을 이용하여 만들어낸 알고리즘이다. 간단히 설명을 하면 홀수인 $n$이 소수인지 알아보면, $n-1$은 짝수가 되고 이는 $n-1 = 2^s d$로 표기를 할수 있다. n이 소수라고 가정해보았을때, 페르마의 소정리를 이용하면 \(a^{n-1} = a^{2^s d} \equiv 1 \: (\text{mod}\: n) \; \exists \, a \in \mathbb{N}, a < n\) 다음 처럼 나타낼 수 있다. 이를 정리하면 $a^{2^r d} = -1 : (\text{mod} : n) \;or\; a^d = 1 : (\text{mod} : n) \;\; some \;r \in [0,s-1]$이 되는데, 다음 두 식을 만족한다면 n은 아마도 소수라고 유추를 할 수 있다.

만약 잘못 판단하는 경우를 줄이고 싶다면 많은 a를 이용하면 된다.

실제 PS 에서 사용할 때에는 int 범위에서 $a = {2,7,61}$ 정도만 확인 하면 되고, long long 범위에서는 $a = {2, 325, 9375, 28178, 450775, 9780504, 1795265022}$로 테스트하는 게 가장 정확하다고 한다.

이때 시간복잡도는 $O(k \log^3 n)$ 이라고 한다.

유클리드 알고리즘 (Euclidean Algorithm)

$ lcm(a,b) = \frac{ab}{gcd(a,b)}$

다음은 유클리드 알고리즘으로 최소공배수 또는 최대공약수 둘중 하나만 알고 있다면 나머지 하나를 알수 있음

유클리드 호제법

다음 사이트에서 효율적 계산 식을 알수 있음

$(a,b) = (b, a \mod b)$ 이 동작하는 이유는 다음 처럼 설명이 가능하다.

$x = \text{gcd} (a,b) $ 일때, $x$는 $a$와 $b$ 모두의 약수이다. 또한 $ a \mod b$의 약수도 된다. 그러므로 수의 크기를 줄여가면서 효율적으로 구현이 가능하다.

Binary Euclid Algorihtm

Binary Euclid Algorithm 이 알고리즘은 현대 컴퓨터는 shift 연산이 더 효율적이기 때문에, 이 shift 연산을 이용하여 조금더 성능이 좋아진 알고리즘이다.

int binary_gcd(int a, int b){
	int k;
	if(a == 0) return b;
	if(b == 0) return a;

	for(k = 0; ((a|b)&1)== 0; ++k){
		a >>= 1;
		b >>= 1;
	}
	while(!(a&1)) a>>=1;
	do{
		while(!(b&1)) b>>=1;
		if(a > b) swap(a,b);
		b -= a;
		b >>=1;
	} while(b);
	return a << k;
}

확장된 유클리드 알고리즘

정수 $a,b$가 주어졌을 때, $ax + by = gcd(a,b)$를 만족하는 $x,y$를 구하는 알고리즘.

이를 구현하기 전 베주 항등식에 대해 알면 이해하기 편하다.

베주 항등식

두 정수의 최대공약수를 원래 두수의 배수의 합으로 나타낼수 있음.

$a,b$가 주어지고, $d$는 $a,b$의 최대공약수일때, 다음 등식을 성립시키는 $m,n$이 존재한다. $ma + nb = d$

이를 통해서 만약 $d \neq k\times \text{gcd}(a,b) \quad k \in \mathbb{\mathbb{N}}$이라면 이는 x,y가 정수해가 아니라는 뜻이다.

다시 확장 유클리드 알고리즘으로 돌아와서…

베주 항등식에 의해 정수해 $x,y$가 존재한다는 것을 알수 있고, 유클리드 알고리즘을 이용하여 $x,y$를 구해낼 수 있다.

확장된 유클리드 알고리즘에서는 정수해 $x,y$를 찾는 것을 중점으로 두기 때문에 반환하는 값도 $x,y$여야 한다.

$\text{gcd}(a,b) = \text{gcd}(b, a \mod b)$

$a \mod b = a-\left \lfloor a/b\right \rfloor \cdot b$

이 두가지 식을 이용하여

$bx’ + (a \mod b)y’ = \text{gcd}(a,b)$ 라고 나타내고, 위의 식을 대입해준다면

$ay’ + b(x’-\left \lfloor a/b\right \rfloor \cdot y’) = gcd(a,b) $ $x = y’ , y = x’-\left \lfloor a/b\right \rfloor \cdot y’$

임을 구할 수 있다.

이를 코드로 구현할때에는 tuple을 이용하여 구할수 있다.

tuple<int,int,int> gcd(int a, int b){
	if (b == 0) return {1,0,a};
	else {
		int x,y, g;
		tie(x,y,g) = gcd(b, a%b);
		return {y, x-(a/b)*y , g};
	}
}
선형 디오판토스 방정식

부정방정식 $ax+by = c$에서 정수해 만을 구하는 방정식을 말한다. 만약 $c = \text{gcd}(a,b)$ 라면 베주 항등식과 동일하다.

디오판토스 방정식에서 정수 해를 구하려면 c가 $\text{gcd}(a,b)$의 배수여야 한다. $c = k \cdot \text{gcd}(a,b)$ 인경우, $ax+by = \text{gcd}(a,b)$를 통해 $x,y$를 구해주고 거기에 $k$를 곱해주면 된다.

또한 디오판토스 방정식의 해는 유일하지 않고, 하나의 해만 알고 있다면 무한히 많은 해를 만들 수 있다.

다음은 그 조합에 해당된다.

\[\left ( x+\frac{pb}{\text{gcd}(a,b)}, y-\frac{pa}{\text{gcd}(a,b)} \right ) , \exists p \in \mathbb{N}\]

빠른 거듭제곱 연산 (Exponentiation by squaring)

정수론 문제에서는 $x^n \mod m$을 빠른 시간에 계산해야하는 경우가 생긴다. 이때 다음 성질을 이용하여 거듭제곱을 빠르게 풀수 있다.

int fast_pow(int x, int n, int m){
	if(n == 0) return 1 % m;
	long long u = fast_pow(x,n>>1, m);
	u = (u*u)%m;
	if(n&1) u = (u*x)%m;
	return u;
}

위의 아이디어를 이용한다면 행렬의 거듭제곱에서 빠르게 사용할 수 있다.

행렬에서의 연산(caculation by matrix)

행렬에서의 빠른 거듭제곱

크기가 $n \times n$인 행렬 $A,B$가 있을 때 $A \times B$ 은 naive 하게 구현하면 $O(n^3)$을 가지게 된다.

strassen’s algorithm

스트라센 알고리즘은 위의 naive 구현보다 조금 더 빠른 $O(n^{2.81})$ 에 작동하는 알고리즘이다. strassen’s algorithm 을 참고할 수 있다.

참고로 현존하는 행렬곱셈 알고리즘 중 가장 빠른 알고리즘은 Le gall의 알고리즘이라고 한다. le gall’s algorithm

다시 거듭제곱으로 돌아온다면..

이번엔 크기가 $n \times n$인 행렬 $A$가 있을 떄 $A^k$를 naive 하게 구현하면 시간이 오래 걸릴 것이다. 이를 보완하기 위해 위의 빠른 거듭제곱의 방식을 이용하여 $O(n^3 \log k)$정도로 줄일 수 있다.

matrix mat_pow(matrix A, int k){
    if( k== 1) return A;
    if( k &1) return A* mat_pow(A,k-1);
    else{
        matrix c = mat_pow(A,k/2);
        return c*c;
    }
}

행렬 제곱의 합

행렬 제곱의 합을 구하는 경우도 있을 텐데, 이도, 빠른 거듭제곱 발상을 이용하여 구할 수 있다. \(S^B=\begin{cases}(A^{B/2}+I)\cdot S^{B/2} &\text{if}~2 \mid B \\(A^{\left\lfloor B/2 \right\rfloor+1}+(A^{\left\lfloor B/2 \right\rfloor+1}+I)\cdot S^{\left\lfloor B/2 \right\rfloor}  & \text{if}~2 \nmid B\end{cases}\)


matrix sum_pow_matrix(int k, matrix A){
    if (k == 1) return A;
    if(k%2 == 1){
        return sum_pow_matrix(k/2,A) * (I + power(k/2, A)) + power(k,A);
    }
    else return sum_pow_matrix(k/2,A) * (I + power(k/2,A));
}

피보나치 수열

피보나치 수열

피보나치 수열은 다양한 형태로 구현을 할수 있으며, 그 방식에는 recursion, iterative, matrix, dp 등 다양하게 구현이 가능하다. 피보나치 수는 식에서 유도가능한 다양한 성질을 가지고 있으므로, 위의 링크를 통해 잘 살펴보는 것이 좋다.

recursion, iterative에서는 $O(N)$ 의 시간이 걸리게 되고, matrix, dp 에서는 $O(\log N)$ 정도 까지 시간을 줄일 수 있다. 그중 map을 이용한 dp 방식의 코드하나만 첨부한다.

#define MOD 1000000007

unordered_map<int64_t, int64_t> Fb;

int64_t Fibo(int64_t N) {
    if (N == 0)    return 0;
    if (N <= 2)    return 1;
    if (Fb.find(N) != Fb.end())    return Fb[N];

    int64_t k;
    if (N & 1) {
        k = (N + 1) / 2;
        return Fb[N] = (Fibo(k) * Fibo(k) + Fibo(k - 1) * Fibo(k - 1)) % MOD;
    }
    else {
        k = N / 2;
        return Fb[N] = (2 * Fibo(k - 1) + Fibo(k)) * Fibo(k) % MOD;
    }
}
제켄도르프의 정리

모든 양의 정수를 한개 이상의 서로 다른 피보나치 수의 합으로, 그 합에 연속된 두 피보나치 수가 포함되지 않도록 유일히 나타낼 수 있다.

모듈러 연산 (Modular arithmetic)

일반적인 연산 (+, -, $\times$) 에서는 연산할때마다 모듈러 연산을 취해주면 된다.

이때 나눗셈인 경우 조금 달라지게 되는데, $(a/b) \mod \;m$인 경우에 $\text{gcd}(b,m) = 1$ 즉 $b,m$이 서로소라면 모듈러 역원 $\text{inv} (b)$를 구한후에 이를 곱해주는 방식으로 연산하게 된다.

오일러 정리

오일러 피 함수(euler’s totient function) $ \varphi (n)$은 1이상 $n$ 이하의 정수중 $n$과 서로소인 정수의 개수를 나타내는 함수이다. 이 값은 $n$을 소인수 분해한 후에 얻어낼 수 있다.

이때 오일러 정리란 모든 서로소인 정수 $x,m$에 대해 다음 식이 성립한다. \(x^{\varphi(m)}\, \text{mod} \,m = 1\) 만약 이식에서 m이 소수인 경우에는 $\varphi(m) = m-1$과 같으므로 \(x^{m-1}\, \text{mod} \,m = 1\) 로 나타낼 수 있고, 이는 페르마 소정리(Fermat’s little theorem)이라고 불린다.

모듈러 역원 (Modular multiplicative inverse)

$a,m$이 서로소일때, $a x \equiv 1\;(\text{mod} \,m)$인 경우 $x$를 모듈러 역원이라고 한다.

이를 통해 모듈러 역원이 존재하는 경우는 $x,m$이 서로소인 경우와 동치가 된다. 그리고 오일러 정리를 이용하면 $\text{inv}(x) = x^{\varphi (m)-1}$과 같다라는 것을 알수 있다. $m$이 소수인 경우에는 $\text{inv}(x) = x^{m-2}$가 된다.

수 $x$의 역원을 구하려면 기본적으로 공식에 의해 오일러 피함수를 구하면 된다. 하지만 이를 구하는 것은 소인수분해를 하고, 그 개수를 세야하므로, 시간이 오래 걸리게 된다. 그러므로 Extened Euclidean Algorithm 을 이용하여 구하게 된다. $ax + my \equiv 1$ 을 이용하면 된다.

Multiple inverses

n개의 정수로 이루어진 수열 $A$의 원소를 $a_i$라고 할때, $a_1 … a_n$까지의 역원을 구하는 방법이다. 기본적으로 역원을 구하는 것은 $O(\log(m))$과 동일하지만 n개의 수의 역원을 구하려면 $O(n\log(m))$이 걸리게 된다. 이를 $O(n+log(m))$안에 처리시키는 알고리즘이다. 하지만 이는 n이 상당히 클때 이득이 있다.

다중역원의 방식을 이용한다면, Factorial 수들에 대한 역원도 전처리로 미리 구해낼 수 있다. 이는 이항계수에서 사용되므로, 한번 계산해보면 좋다.

vector<int> m_inv(vector<int> A, int m){
	int n = A.size();
	vector<int> B(n);
	B[0] = A[0];
	for(int i = 1; i<n;i++){
		B[i] = A[i]*B[i-1];
	}
	int inv,y,g;
	tie(inv,y,g)= gcd(B[n-1],m);

	vector<int> ans(n);
	ans[n-1] = inv;
	for(int i = n-2; i>=1;i--){
		ans[i] = A[i+1]*ans[i+1];
	}
	tie(inv,y,g) = gcd(A[0],m);
	ans[0] = inv;
	return ans;

}

다음처럼 식을 구성해볼수 있다.

중국인의 나머지 정리 (Chinese Remainder Theorem)

$x=a_i \, \text{mod}\;m_i \;,\; \forall i\in [1,n] $ 인 문제가 나온다면 사용할 수 있는데, 이때 $m_i$의 모든 조합은 서로소이다. 중국인의 나머지 정리 증명에서 자세하게 증명을 할 수 있다.

일단 $n_i$가 모두 서로소여야 하는 것이 전제이다. N을 $n_i$의 곱이라고 생간한다. 그리고 $N_i = \frac{N}{n_i}$ 라고 생각한다면 $N_i, n_i$는 서로소가 된다. 이는 $N$에서 $n_i$만 없어지는 것이기 때문에 무조건 서로소가 된다. 그럼 베주 항등식에 의해 $M_i N_i + m_i n_i = 1$인 정수해 $M_i, m_i$를 찾을 수 있다. 그럼 우리가 구해야하는 값 x는 $\sum a_iM_iN_i$가 된다. 이 식을 다음 합동식으로 바꿀 수 있다. 이 때 $M_iN_i \equiv 1 \mod n_i$ 이 식이 나오게 된다. 즉 $M_i$는 $N_i$의 역원인것을 알수 있고, 확장 유클리드 알고리즘을 이용하여 역원을 구할 수 있다. 그리고 위의 x를 구하고, N으로 모듈러연산을 취해주면 된다.

int mod(int x, int n){
	x %= n;
	if(x<0) x+= n;
	return x;
}

tuple<int,int,int> gcd(int a, int b){
	if (b == 0) return {1,0,a};
	else {
		int x,y, g;
		tie(x,y,g) = gcd(b, a%b);
		return {y, x-(a/b)*y , g};
	}
}

int crt(vector <int> a, vector<int> n){
	int NN = 1;
	vector <int> N(n.size());
	for(auto i : n) NN *= i;
	for(int i = 0;i<N.size();i++){
		N[i] = NN/n[i];
	}

	vector<int> M(N.size());
	for(int i = 0; i<M.size();i++){
		int y, g;
		tie(M[i],y,g) = gcd(N[i],n[i]);
	}
	int x = 0;
	for(int i = 0;i<M.size();i++){
		x += a[i]*N[i]*M[i];
	}
	x = mod(x,NN);
	return x;
}
서로소가 아니라면?

서로소가 아닌 경우도 존재하는데, 이때는 이 링크에서 나온 공식을 토대로 연쇄적으로 $x$를 풀어나가면 된다.

int mod(int x, int n){
	return (x+n)%n;
}

int _gcd(int a, int b){
	if(b == 0) return a;
	return gcd(b, a%b);
}

tuple<int,int,int> gcd(int a, int b){
	if (b == 0) return {1,0,a};
	else {
		int x,y, g;
		tie(x,y,g) = gcd(b, a%b);
		return {y, x-(a/b)*y , g};
	}
}

int crt(vector <int> a, vector<int> m){
	int n = m.size();
	int a1 = a[0], m1 = m[0];
	a1 %= m1;
	for(int i = 1; i<n;i++){
		int a2 = a[i], m2 = m[i];
		int g = _gcd(m1,m2);
		if(a1%g != a2%g) return -1;
		auto [x,y,_] = gcd(m1/g,m2/g);
		int M = m1 / g * m2;
		a1 = (a1*(m2/g)%M) * y % M + (a2*(m1/g)%M)*x % M;
		a1 = mod(a1, M);
		m1 = M;
	}
	return a1;
}

꼭 문제를 풀때, 조건이 서로소인지, 아닌지를 생각하고 두개의 코드를 잘 이용해야한다.

진법변환 (base conversion)

BOJ 1112 이 문제를 보면 일반적인 진법변환이 아닌, 음수 진법변환도 포함되어 있는 경우가 있다. 이런 경우에는 어떻게 해야할지 수학적으로 접근을 해보아야한다.

\[x = a_nb^n+ a_{n-1}b^{n-1}+...+a_1b+a_0\]

우리는 어떤 10진수 $x$를 위에 처럼 표기할 수 있다. 일단 $x,b$가 양수일때 생각해보자.

모듈러 연산을 이용하면 다음을 알아낼 수 있다.

\[m_0 = x \equiv a_0 \mod b\]

그리고 위의 식을 다음처럼 변형할 수 있다.

\[\frac{m_0-a_0}{b} = a_nb^{n-1}+ a_{n-1}b^{n-2}+...+a_1\]

이 식은 다음 합동식이 된다. \(m_1 = \frac{m_0-a_0}{b} \equiv a_1 \mod b\)

즉 일반화를 시켜보면 다음처럼 만들 수 있다.

\[m_i = \frac{m_{i-1}-a_{i-1}}{b} \equiv a_i \mod b\]

이를 이용하면 $a_i$를 모두 구할 수 있고, $a$를 뒤집으면 진법이 변환이 된다.

이제 $x$가 음수고 $b$가 양수인 경우 생각해보면, 위에서 그대로 구하고, 앞에 마이너스 부호만 붙여주면 된다.

그럼 b가 음수인 경우를 생각해보자. b가 음수인경우 모듈러가 음수인 경우는 불가능 하기 때문에, $|b|$로 모듈러를 취해주면 된다. 나머지 과정은 위의 증명을 통해 똑같다는 것을 알 수 있다.

pair<bool,vector<int>> crt(int n, int b){
    vector <int> ans;

    bool p = b>0 && n<0;
    n = b>0 ? abs(n):n;
    while(n!=0){
        ans.push_back( mod(n,abs(b)));
       n =((n-ans.back())/b);
    }
    if(ans.empty()) ans.push_back(0);
    reverse(ans.begin(), ans.end());
    return {p, ans};
}