알고리즘 문제/소수
[개쉬운 풀이] 백준 22943 수 (CPP/C++)
odaebum
2024. 3. 4. 15:02
728x90
https://www.acmicpc.net/problem/22943
생각
해당 문제에서 시각적으로 주어진 조건은 2개이지만 총 3가지 조건을 이용하여 해결해야한다.
- 0부터 9까지 K가지의 숫자를 한 번씩만 사용하여 만들 수 있는 수
- 서로 다른 두 개의 소수의 합으로 나타낼 수 있는 경우
- M으로 나누어 떨어지지 않을때까지 나눈 수가 두 개의 소수의 곱인 경우, 이 때, 두 개의 소수가 같아도 된다.
해당 문제에서 소수를 사용해야 하므로 '에라토스테네스의 체'를 사용한다.
(문제에 소수가 나오면 그냥 아리스토텔레스든 에라토스뭐시기든 해당 체를 사용해야한다고 무조건 염두한다)
에라토스테네스의 체 구현
bool primeNum[100001];
void getPrime(){
memset(primeNum, true, sizeof(primeNum));
primeNum[0] = false;
primeNum[1] = false;
for(int i = 2; i*i <= 100000; i++){
if(primeNum[i]){
for(int k = i*i; k <= 100000; k += i){
primeNum[k] = false;
}
}
}
}
bool 형식의 배열을 사용하여 해당 숫자가 소수인지 아닌지를 true, false로 나타낼 수 있게 하였다.
코드
먼저 각 조건을 구현하였다.
1번 조건 :
int K;
bool usedNum[10]; //숫자를 생성하면서 0 ~ 9의 서로 다른 숫자를 사용하기 위함
bool numbers[100001]; //주어진 숫자의 최대 자릿수는 5자리 이므로 해당 번호만큼
//bool 형태로 설정하여 나중에 조건에 부합한지 안 한지 판단할 수 있도록 함
void getNumbers(int count, string num){
// 내가 원하는 자릿수(K)만큼의 길이가 되면 해당 숫자를 저장
if(count == K){
numbers[stoi(num)] = true;
return ;
}
//0 ~ 9 자리를 사용할 수 있게 포문사용
for(int i = 0; i <= 9; i++){
if(count == 0 && i == 0) continue; //첫 자리수가 0이 되면 안되므로
if(usedNum[i]) continue; //서로 다른 수를 판단해야하므로 해당 배열로 확인
usedNum[i] = true; //사용알림
getNumbers(count+1, num+to_string(i));
usedNum[i] = false; //다음을 위하여 삭제 (재귀 이후에는 다시 제자리로 돌려놔야함)
}
}
재귀함수로 구현하였으며 모든 숫자에 대해 string 형태로 저장하고 반환하여 쉽게 접근할 수 있게하였다.
(숫자 자릿수로 가지고 노는 문제는 string으로 처리하는게 편함)
2번 조건 :
void rule1(){
//완전탐색으로 하나씩 그 숫자들이 내 조건에 부합한지 확인
for(int i = 0; i < 100001; i++){
if(!numbers[i]) continue; //이미 걸러진 숫자는 스킵
numbers[i] = false;
for(int j = 2; j < i/2 + 1; j++){ //어차피 2와 5를 비교하는거나 5와 2를 비교하는거는 같기 때문에 반절만 해서 시간 단축
// 1) 해당 숫자가 소수인가?
if(primeNum[j]){
// 2) 다른 수(내가 궁금한 숫자 - 현재 숫자)가 소수인가?
// 3) 위 두 수가 서로 다른 수인가?
if(primeNum[i-j] && j != (i-j)){
numbers[i] = true; //해당 조건에 부합하면 다시 true 부여
break;
}
}
}
}
}
2번째 포문에서 시간초과를 고려하여 소수중에서 어느 범위까지 탐지할지 범위설정을 잘 해주어야함
해당 부분에서 시간초과 발생 가능
3번 조건 :
int M;
// M으로 더 이상 나눠지지 않는 수
int getdividenum(int num, int M){
if(num < M) return num;
while(true){
if(num%M != 0) return num;
num = num/M;
}
return num;
}
void rule2(){
for(int i = 0; i < 100001; i++){
if(!numbers[i]) continue;
numbers[i] = false;
int tmp = getdividenum(i,M); // 1) M으로 더이상 나눠지지 않는 수 인가?
for(int j = 0; j*j < i + 1 ; j++){ //sqrt()함수보다 그냥 j*j로 표기하는게 더 빠름
// 2) 소수인가?
if(primeNum[j]){
// 3) 곱셈으로 표현 가능한가? (나누어 떨어지는가?)
// 4) 소수인가?
if(tmp%j == 0 && primeNum[tmp/j]){
numbers[i] = true;
break;
}
}
}
}
}
2번째 포문에서 시간초과를 고려하여 소수중에서 어느 범위까지 탐지할지 범위설정을 잘 해주어야함
해당 부분에서 시간초과 발생 가능
전체 코드
#include <iostream>
#include <vector>
#include <string>
#include <cmath>
#include <algorithm>
#include <memory.h>
using namespace std;
int K, M;
bool numbers[100001] = {false};
bool usedNum[10] = {false};
bool primeNum[100001];
void getPrime(){
memset(primeNum, true, sizeof(primeNum));
primeNum[0] = false;
primeNum[1] = false;
for(int i = 2; i * i <= 100000; i++){
if(primeNum[i]){
for(int k=i*i; k <= 100000; k+=i){
primeNum[k] = false;
}
}
}
}
void input(){
cin >> K >> M;
}
void rule1(){
for(int i = 0; i < 100001; i++){
if(!numbers[i]) continue;
numbers[i] = false;
for(int j = 2; j < i/2 + 1; j++){
if(primeNum[j]){
if(primeNum[i-j] && j != (i-j)){
numbers[i] = true;
break;
}
}
}
}
}
int getdividenum(int num, int M){
if(num < M) return num;
while(true){
if(num%M != 0) return num;
num = num/M;
}
return num;
}
void rule2(){
for(int i = 0; i < 100001; i++){
if(!numbers[i]) continue;
numbers[i] = false;
int tmp = getdividenum(i,M);
for(int j = 0; j*j < i + 1 ; j++){
if(primeNum[j]){
if(tmp%j == 0 && primeNum[tmp/j]){
numbers[i] = true;
break;
}
}
}
}
}
void getNumbers(int count, string num){
if(count == K){
numbers[stoi(num)] = true;
return ;
}
for(int i = 0; i <= 9; i++){
if(count == 0 && i == 0) continue;
if(usedNum[i]) continue;
usedNum[i] = true;
getNumbers(count+1, num+to_string(i));
usedNum[i] = false;
}
}
int sol(){
int result = 0;
getPrime();
getNumbers(0,"");
rule1();
rule2();
for(int i = 0; i < 100001; i++){
if(numbers[i]){
result++;
}
}
return result;
}
int main(){
ios_base::sync_with_stdio(false);cin.tie(0);cout.tie(0);
input();
cout << sol();
return 0;
}
결론 :
1. 소수를 다루는 문제이므로 에라토스테네스의 체를 사용하자
2. 수 하나를 비교해야하는 문제 맞다 완전탐색으로 풀어도 되나라고 고민할 시간에 일단 풀자
3. 아무리 완전탐색이라고 해도 시간을 줄일 수 있는 방안을 생각하면서 풀자.
728x90