Processing math: 96%
본문 바로가기

카테고리 없음

백준 7501번: Key

https://www.acmicpc.net/problem/7501

 

7501번: Key

Hackers often have to crack passwords for different data encryption systems. A novice hacker Bill faced such a problem one day. After performing several experiments he noticed regularity in the formation of an encryption key. He knew that a key is an odd i

www.acmicpc.net


케이스 분류를 차근차근 해보면 쉽게 접근할 수 있는 문제입니다.


Case 1 : K가 소수일 때

Wilson's Theorem에 따르면,

(p1)!1(modp)

입니다. (p1)!p의 배수가 아니므로 p2으로 divisible 할 수가 없습니다.

 

따라서, KP 이면 반드시 문제에서 말하는 key에 해당합니다. 단, K가 2인 경우는 제외해야 하는데, 사실 입력되는 범위부터 2는 배제되어 있습니다.

 

다른 방법으로, (p1)! 의 소인수분해에서 p가 한 번도 등장하지 않는 것을 눈치챌 수도 있고, 이 방법이 다른 케이스에서 효과적으로 사용됩니다.


Case 2 : K의 소인수분해가 pe 일 때

Legendre's Formula 에 따르면, n! 의 소인수분해에서 소수 p 의 등장 횟수는

vp(n!)=i=1npi

입니다. 이를 활용할 수 있게 문제를 조금 변형하겠습니다.

 

(pe)!p3e 로 divisible하지 않은 경우가 존재하는가?

 

변형한 문제가 원래 문제와 같음을 쉽게 알 수 있습니다. 변형 문제에 대해 Legendre's Formula 를 적용하면

vp(pe!)=pe1p1

이제 변형 문제의 답은

 

소수 pe2 에 대해 pe1p1<3e 를 만족하는 경우가 존재하는가?

 

의 답이며, 식을 풀어보면

p=2,e=2,3

p=3,e=2

뿐임을 얻을 수 있습니다. 한편, 원래 문제는 key가 반드시 홀수여야 한다고 했으므로, 이 케이스에서 K=9 만이 valid 합니다.


Case 3 : K가 소인수가 2개 이상인 홀수 합성수일 때

K 의 소인수분해를 다음과 같이 가정합니다.

K=pe11×pe22××pedd

이때 Legendre's Formula 에 의해

vpi(K1)!K1pi=Kpi12(1id)

따라서 이러한 K 는 반드시 key일 수 없습니다.


Appendix : K가 소인수가 2개 이상인 짝수 합성수일 때

문제에서는 필요없지만, 증명의 완결을 위한 마지막 케이스입니다. K 의 소인수분해를 다음과 같이 가정합니다.

K=2x×pe11××pedd

(1) d = 1, x = 1

K=2p 로 놓으면, (2p1)! 을 전개했을 때 p 의 배수는 p 가 유일합니다. 따라서 K=2p 는 key에 해당합니다. (p3)

 

그러면, e2 에 대해 K=2pe 이면 어떨까요? (단, e2)

vp((2pe1)!)=i=12pe1pi=2(pe1+pe2++p0)p

이로부터 K=2pe 가 key에 해당할 조건은

pe1<p12×(p+2e)

입니다만, 어떤 p,e 도 이를 만족하지 않습니다. 즉, K=2pe 는 key에 해당하지 않습니다.

(2) d = 1, x > 1

먼저, K=2xp 로 놓고 Legendre's Formula 를 사용합니다.

v2((2xp1)!)p(2x1++20)p=p(2x2)

이므로 key일 조건은

p<x2x11

입니다. 하지만, x>1 이므로 위 부등식을 만족하는 소수 p 는 없습니다.

 

따라서, K=2xp 또는 K=2pe 가 모두 key 가 될 수 없으며

 

다른 말로는 d=1 에서 K=2p 를 제외하면 key가 없습니다.

(3) otherwise

같은 아이디어로 증명할 수 있습니다. 결론은, 이러한 K 는 key에 해당하지 않습니다.


이상으로부터 결론을 내릴 수 있습니다.

 

K=p 또는 K=2p 또는 K=4,8,9 일 때만이 K^{2} \nmid (K-1)! 을 만족한다.

 

하지만, 문제에서 원하는 건 홀수 key 이므로, 결국 문제는 주어진 범위에서 소수 판정을 효율적으로 하는 방법을 묻는 것입니다.

 

주어지는 범위가 최대 10^{18} 이므로, deterministic 한 Miller-Rabin Test 를 통해 소수 판정을 진행합니다.

 

아래는 제 코드입니다.

#include <iostream>
#include <vector>

namespace Primality {
	using u32 = unsigned int;
	using u64 = unsigned long long;
	
	bool sqrt_test (u64 N) {
		/* input value : N is odd and small 
		 * return value : {false := composite; true := prime}
		 * time complexity : sqrt(N)
		 * restriction : N < 10^9
		 */
		for (u32 i = 3; i * i <= N; i += 2) {
			if (N % i == 0) return false;
		}
		return true;
	}
	
	u64 power_modular (u64 X, u64 Y, u64 M) {
		u64 result = 1;
		while (Y) {
			if (Y % 2) {
				result = result * X % M;
			}
			X = X * X % M;
			Y /= 2;
		}
		return result;
	}
	
	using i128 = __int128;
	u64 power_modular_safe (u64 X, u64 Y, u64 M) {
		u64 result = 1;
		while (Y) {
			if (Y % 2) {
				result = (i128)result * X % M;
			}
			X = (i128)X * X % M;
			Y /= 2;
		}
		return result;
	}
	
	bool Miller_Rabin_Internal (u64 N, u64 K) {
		/* input value : N is odd, K < N
		 * return value : {false := composite; true := probable-prime}
		 *
		 * explanation :
		 * calculate k^d, k^(2d), k^(4d), ... , k^(2^s-1 * d)
		 * where 2^s * d = N - 1
		 * 
		 * Then, N is probably prime if k^d = +1/-1 or k^(2^k * d) = -1 mod N
		 * 
		 * time complexity : O(log^2 N)
		 */
		u64 d = N - 1;
		for (; d % 2 == 0; d /= 2) {
			if (power_modular_safe(K, d, N) == N - 1) {
				return true;
			}
		}
		if (u64 temp = power_modular_safe(K, d, N); temp == 1 || temp == N - 1) {
			return true;
		}
		else {
			return false;
		}
	}
	
	bool Miller_Rabin_test (u64 N) {
		/* input value : N is odd and large
		 * return value : {false := composite; true := prime}
		 *
		 * explanation :
		 * Originally, Miller_Rabin_Test can be wrong by 4^-k where k is number of rounds
		 * But, for small N, there is a deterministic way which has 100% accuracy
		 *
		 * time complexity : O(k log^2 N)
		 */
		std::vector<u64> list = {2, 7, 61};
		if (N > ((u64)1 << 32)) list =  {2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37};
		
		for (auto i : list) {
			if (N % i == 0) return false;
			if (!Miller_Rabin_Internal(N, i)) return false;
		}
		return true;
	}
	
	bool is_Prime (u64 N) {
		if (N < 2) return false;
		if (N == 2 || N == 3) return true;
		if (N % 6 != 1 && N % 6 != 5) return false;
		if (N <= 1e4) return sqrt_test(N);
		return Miller_Rabin_test(N);
	}
}

using namespace std;
int main() {
	ios_base::sync_with_stdio(false);
	cin.tie(nullptr);
	cout.tie(nullptr);
	
	long long A, B;
	cin >> A >> B;
	
	for (long long i = A; i <= B; i++) {
		if (Primality::is_Prime(i) || i == 9) {
			cout << i << ' ';
		}
	}
	
	return 0;
}

(C++20, 0ms, 2024KB, 제출번호 67726620)