1644번 소수의 연속합
하나 이상의 연속된 소수의 합으로 나타낼 수 있는 자연수들이 있다. 몇 가지 자연수의 예를 들어 보면 다음과 같다.
하지만 연속된 소수의 합으로 나타낼 수 없는 자연수들도 있는데, 20이 그 예이다. 7+13을 계산하면 20이 되기는 하나 7과 13이 연속이 아니기에 적합한 표현이 아니다. 또한 한 소수는 반드시 한 번만 덧셈에 사용될 수 있기 때문에, 3+5+5+7과 같은 표현도 적합하지 않다.
자연수가 주어졌을 때, 이 자연수를 연속된 소수의 합으로 나타낼 수 있는 경우의 수를 구하는 프로그램을 작성하시오.
첫째 줄에 자연수 N이 주어진다. (1 ≤ N ≤ 4,000,000)
첫째 줄에 자연수 N을 연속된 소수의 합으로 나타낼 수 있는 경우의 수를 출력한다.
20
0
🔥 문제 요약
연속된 소수들의 합으로 입력받은 숫자 N을 만드는 경우의 수를 출력한다.
🤔 생각해야 할 점
→ N까지의 소수를 전부 구해서 합이 N이 되는 부분 집합을 찾는다.
⇒ 투 포인터가 유리한 문제
🔥 풀이 과정



#include<iostream>
#include<vector>
using namespace std;
vector<int> v;
bool isPrime(int num) {
if (num <= 1) //1은 소수가 아니니까
return false;
for (int i = 2; i * i <= num; i++) {
if (num % i == 0)//나누어 떨어지는 수가 있으면 소수가 아니다.
return false;
}
return true;
}
int main() {
int N;
cin >> N;
for (int i = 2; i <= N; i++) {//2~N까지의 소수를 벡터에 저장
if (isPrime(i)) {//소수
v.push_back(i);
}
}
//투포인터
int start = 0;
int end = 0;
int sum = 0;
int answer = 0;
while (start <= v.size() && end <= v.size()){
if (sum < N) {//합이 주어진 소수보다 작으면
sum += v[end];//end인덱스의 값을 합에 더해줌
end++;
}
else if (sum > N) {//합이 주어진 소수보다 커지면
sum -= v[start];//start인덱스의 값을 합에서 빼고
start++;//start인덱스 이동
}
else if (sum == N) {//합과 소수가 같을 때
answer++;
sum -= v[start];
start++;//start의 값을 빼주니까 다시 주어진 소수보다 작은 상태가 된다.
}
}
cout << answer << endl;
return 0;
}
i<N 으로 설정해서 소수를 하나 빼고 구해서 계속 결과가 하나 모자라게 나왔다.#include<iostream>
#include<vector>
using namespace std;
vector<int> v;
bool isPrime(int num) {
if (num <= 1) //1은 소수가 아니니까
return false;
for (int i = 2; i * i <= num; i++) {
if (num % i == 0)//나누어 떨어지는 수가 있으면 소수가 아니다.
return false;
}
return true;
}
int main() {
int N;
cin >> N;
for (int i = 2; i <= N; i++) {//2~N까지의 소수를 벡터에 저장
if (isPrime(i)) {//소수
v.push_back(i);
}
}
//투포인터
int start = 0;
int end = 0;
int sum = 0;
int answer = 0;
while (start < v.size()){
if (sum < N) {//합이 주어진 소수보다 작으면
sum += v[end];//end인덱스의 값을 합에 더해줌
end++;
if(end > v.size())
break;
}
else if (sum > N) {//합이 주어진 소수보다 커지면
sum -= v[start];//start인덱스의 값을 합에서 빼고
start++;//start인덱스 이동
}
else if (sum == N) {//합과 소수가 같을 때
answer++;
sum -= v[start];
start++;//start의 값을 빼주니까 다시 주어진 소수보다 작은 상태가 된다.
}
}
cout << answer << endl;
return 0;
}
✅ 소수를 구하는 함수
bool isPrime(int num) {
if (num <= 1) //1은 소수가 아니니까
return false;
for (int i = 2; i * i <= num; i++) {
if (num % i == 0)//나누어 떨어지는 수가 있으면 소수가 아니다.
return false;
}
return true;
}
✅ 2~N까지의 수를 검사해 그 중 소수를 v벡터에 넣는다.
for (int i = 2; i <= N; i++) {//2~N까지의 소수를 벡터에 저장
if (isPrime(i)) {//소수
v.push_back(i);
✅ 연속하는 소수의 합이 N이 되는 것을 찾기 위해 2개의 포인터를 설정해 합을 구한다.
while (start < v.size()){
if (sum < N) {//합이 주어진 소수보다 작으면
sum += v[end];//end인덱스의 값을 합에 더해줌
end++;
if(end > v.size())
break;
}
else if (sum > N) {//합이 주어진 소수보다 커지면
sum -= v[start];//start인덱스의 값을 합에서 빼고
start++;//start인덱스 이동
}
else if (sum == N) {//합과 소수가 같을 때
answer++;
sum -= v[start];
start++;//start의 값을 빼주니까 다시 주어진 소수보다 작은 상태가 된다.
}
}
🔥 어려웠던 점
while의 범위를 설정하는것, 반복문의 종단 조건 설정에 어려움이 있었다. 투포인터는 비슷한 문제를 여러번 계속 풀다보니까 금방 생각이 났는데 범위 설정이 힘들었다.
처음엔 반복문의 조건을 while (start <= v.size() && end <= v.size()) 이렇게 설정했는데 이렇게 되면 v.size()의 범위를 벗어나게 되어 런타임 에러가 발생한다. 하지만 그렇다고 while문의 조건을 end < v.size()로 설정하면 반복문이 완전히 돌지 않아서 그런지 경우의 수에서 계속 1이 빠진 값이 나왔다.
start < v.size() 만 설정하면 end가 범위를 벗어나게 되어 런타임 에러가 발생했기 때문에 결국에는 start < v.size()를 while문의 조건으로 설정하고 end를 증가시키는 if문에 따로 end > v.size()의 조건을 추가해서 end가 범위를 벗어나면 while문을 빠져나가도록 했다.
아오~~
while의 범위 설정
내가 코드에서 쓴 소수를 구하는 코드는 2~N의 모든 소수를 구하면서 2~n의 제곱근까지 모든 값에 대해 나누어 떨어지는지 검사한다. 근데 이 코드는 시간이 오래 걸리는 비효율적인 코드라는 것을 알게 되었다.
⭐️아라토스테네스의 체를 사용해서 코드를 작성하면 더 효율적으로 빠른 시간안에 해결할 수 있다.
for (int i = 2; i < N; i++){
if (check[i] == false){//i번째 인덱스가 소수라면
for (int j = i + i; j <= N; j += i){//i의 배수들은
check[j] = true;//소수가 아니다.
}
}
}
확인을 안한거면 소수이다.
예를 들어 i인덱스에서 2가 소수임을 알게 되면 2의 배수들 (2, 4, 6,...)은 소수가 아니다.
->체에 거르듯이 걸러지게 된다.