문제
1보다 큰 자연수 중에서 1과 자기 자신을 제외한 약수가 없는 자연수를 소수라고 한다. 예를 들어, 5는 1과 5를 제외한 약수가 없기 때문에 소수이다. 하지만, 6은 6 = 2 × 3 이기 때문에 소수가 아니다.
골드바흐의 추측은 유명한 정수론의 미해결 문제로, 2보다 큰 모든 짝수는 두 소수의 합으로 나타낼 수 있다는 것이다. 이러한 수를 골드바흐 수라고 한다. 또, 짝수를 두 소수의 합으로 나타내는 표현을 그 수의 골드바흐 파티션이라고 한다. 예를 들면, 4 = 2 + 2, 6 = 3 + 3, 8 = 3 + 5, 10 = 5 + 5, 12 = 5 + 7, 14 = 3 + 11, 14 = 7 + 7이다. 10000보다 작거나 같은 모든 짝수 n에 대한 골드바흐 파티션은 존재한다.
2보다 큰 짝수 n이 주어졌을 때, n의 골드바흐 파티션을 출력하는 프로그램을 작성하시오. 만약 가능한 n의 골드바흐 파티션이 여러 가지인 경우에는 두 소수의 차이가 가장 작은 것을 출력한다.
입력
첫째 줄에 테스트 케이스의 개수 T가 주어진다. 각 테스트 케이스는 한 줄로 이루어져 있고 짝수 n이 주어진다.
출력
각 테스트 케이스에 대해서 주어진 n의 골드바흐 파티션을 출력한다. 출력하는 소수는 작은 것부터 먼저 출력하며, 공백으로 구분한다.
제한
- 4 ≤ n ≤ 10,000
추상화
1) 문제이해: 골드바흐의 파티션: 짝수를 두 소수의 합으로 나타낼 수 있다. n이 주어지면 차이가 가장 작은 두 소수를 출력한다
2) n <= 10,000인 제한조건을 활용하여 소수 배열을 만든다
3) n보다 작은 소수를 고르고 small이라고 한다. n-small을 계산하고 large라고 한다
4) 문제의 조건에 알맞은 small과 large값을 찾아 출력한다
<주의사항>
→ small은 끝까지 탐색할 필요가 없다: 4 + 17이나 17 + 4나 같기 때문이다.
이번 풀이에서는 10000개의 숫자에서 소수만 따로 뺀 배열을 만들었다.
small은 두 소수 중 작은 숫자기 때문에 늘 n의 절반 크기보다 작거나 같다
smalll의 크기는 10000의 절반인 5000을 넘을 수 없다
→ large가 소수인지 아닌지 판별해야한다. 소수가 맞다면 둘의 차이를 계산해 그 차이를 계산해주고 판별해야한다
<단계별 풀이>
1) 데이터 크기가 10000인 논리배열에서 소수들만 false로 만든다
2) small은 5000보다 작거나 같으므로 탐색범위를 좁힌다
3) 받은 n값에 대해 탐색을 시작한다. for내부에서 증가하는 j와 n-j index가 false(소수)라면 값을 저장한다
4) 차이를 계산하고 그 차이가 최소값인지 판별한다
5) 최소값을 갱신할 때마다 출력할 변수(smallR, largeR)에 담는다
5) 반복문이 끝날 때 smallR과 largeR을 출력한다
import java.io.*;
public class Main {
public static void main(String[] args) throws IOException{
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));
boolean[] prime = new boolean[10001];
int T, n;
int small = 0, large=0, smallR=0, largeR=0;
//아래에서 사용할 변수들을 미리 정의해준다
int devide =0;
for(int i=2; i<10001; i++) {
devide = 10000/i;
for(int j=2; j<=devide; j++) {
prime[i*j] = true;
}
}
prime[0] = true;
prime[1] = true;
// 소수는 false의 값을 가지고 있다
T = Integer.parseInt(br.readLine());
for(int i =0; i<T; i++) {
int diff = 10000;
int diffR = 10000;
n = Integer.parseInt(br.readLine());
for(int j =2; j<=n/2; j++) {
if(prime[j] == false && prime[n-j]==false) {
//값이 동시에 소수라면 저장된다
small = j;
large = n-j;
diff = large - small;
}
if(diff < diffR) {
smallR = small;
largeR = large;
diffR = diff;
}
//현재 계산해준 차이가 최소값보다 작으면 출력할 변수들에 기록한다
}
System.out.println(smallR+" "+largeR);
//출력
}
}
}
다양한 시도들
1) 소수로만 이루어진 배열을 만들어서 탐색하면 더 빨라지지 않을까 시도해보았다
10,000까지의 숫자 중 소수의 개수는 1229개여서 이 크기의 배열을 만들고 5000보다 커지는 지점의 index를 구했다(669)
하지만 입력값으로 주어진 n을 효율적으로 사용할 수 없다는 단점이 있었다
위의 경우 j, n-j를 통해 바로 소수인지 판별할 수 있었지만 소수들을 따로 모아두는 경우 일일히 대조해야하기 때문이었다
import java.io.*;
public class Main {
public static void main(String[] args) throws IOException{
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));
boolean[] prime = new boolean[10001];
int[] net = new int[1229];
int T, n;
int index = 0;
int small, large, smallR=0, largeR=0;
int devide =0;
for(int i=2; i<10001; i++) {
devide = 10000/i;
for(int j=2; j<=devide; j++) {
prime[i*j] = true;
}
}
prime[0] = true;
prime[1] = true;
for(int i = 0; i<10001; i++) {
if(prime[i]==false) {
net[index] = i;
index++;
}
}
T = Integer.parseInt(br.readLine());
for(int i =0; i<T; i++) {
int diffmin =10000;
int diff = 10000;
n = Integer.parseInt(br.readLine());
for(int j = 0; j<669; j++) {
small = net[j];
large = n - small;
if(small<=large) {
for(int k =0; k<1229; k++) {
if(large == net[k]) {
diff = large -small;
}
if( diff < diffmin) {
diffmin = diff;
smallR = small;
largeR = large;
}
}
}
}
System.out.println(smallR+" "+largeR);
}
}
}
2) 차이가 최소값인 소수를 출력한다에 집중한다
탐색할 때 처음부터 끝까지 탐색하곤 한다. 하지만 조건을 풀어서 해석하면 다음과 같다
i) n이 주어지는데 서로 더해서 n이 되는 숫자 a와 b를 구하여라
ii) 숫자 a와 b의 차이가 최소인 것을 구하라 = a와 b의 값이 가까운 것을 구하여라
→ 가장 이상적인 경우: a = b = n/2
iii) 중앙에서 탐색을 시작해서 먼저 만들어지는 a와 b가 곧 최소의 차이를 가지는 소수이다
Red: 일반적인 탐색 방향 / Blue: 중앙부터 탐색
import java.io.*;
public class Main {
public static void main(String[] args) throws IOException{
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));
boolean[] prime = new boolean[10001];
int T, n;
int small = 0,large=0;
int devide =0;
for(int i=2; i<10001; i++) {
devide = 10000/i;
for(int j=2; j<=devide; j++) {
prime[i*j] = true;
}
}
prime[0] = true;
prime[1] = true;
T = Integer.parseInt(br.readLine());
for(int i =0; i<T; i++) {
n = Integer.parseInt(br.readLine());
int middle_left = n/2;
int middle_right = n/2;
//중앙값부터 왼쪽, 오른쪽으로 나아갈 변수 선언
while(true) {
if(prime[middle_left] == false && prime[middle_right]==false) {
small = middle_left;
large = middle_right;
break;
//최소값을 구했으므로 즉시 나온다
}
middle_left--;
middle_right++;
//검사를 마치면 각각의 방향으로 이동
}
System.out.println(small+" "+large);
}
}
}
* 코드는 짧아졌지만 메모리가 커진 모습
'Study > Baekjoon' 카테고리의 다른 글
Baekjoon3009: 네 번째 점 (0) | 2021.10.14 |
---|---|
Baekjoon1085: 직사각형에서 탈출 (0) | 2021.10.14 |
Baekjoon4948: 베르트랑 공준 (0) | 2021.10.13 |
Baekjoon1929: 소수 구하기 (0) | 2021.10.13 |
Baekjoon11653: 소인수분해 (0) | 2021.10.13 |