알고리즘 · 30
최대공약수, 소수, 진법
나머지로 최대공약수를 구하고, 체로 소수를 걸러 내고, 수를 다른 진법으로 써요.
수를 다루는 문제에 자주 나오는 도구 세 가지예요.
아래 예제는 84와 36의 최대공약수를 구하고, 30 이하의 소수를 모두 찍고, 255를 16진법으로 써요.
DIGITS = "0123456789abcdefghijklmnopqrstuvwxyz" def gcd(a, b): while b != 0: a, b = b, a % b # (a, b)를 (b, a를 b로 나눈 나머지)로 바꾼다 return a def to_base(x, base): if x == 0: return "0" s = "" while x > 0: s = DIGITS[x % base] + s # 나머지는 낮은 자리부터 나오므로 앞에 붙인다 x //= base return s a, b = map(int, input().split())n = int(input())x, base = map(int, input().split()) print(gcd(a, b)) # 체: 소수를 찾을 때마다 그 배수를 지운다. 끝까지 안 지워진 수가 소수is_prime = [True] * (n + 1)is_prime[0] = is_prime[1] = Falsei = 2while i * i <= n: if is_prime[i]: for j in range(i * i, n + 1, i): is_prime[j] = False i += 1primes = [i for i in range(2, n + 1) if is_prime[i]]print(*primes) print(to_base(x, base))const DIGITS = "0123456789abcdefghijklmnopqrstuvwxyz"; function gcd(a, b) { while (b !== 0) { [a, b] = [b, a % b]; // (a, b)를 (b, a를 b로 나눈 나머지)로 바꾼다 } return a;} function toBase(x, base) { if (x === 0) return "0"; let s = ""; while (x > 0) { s = DIGITS[x % base] + s; // 나머지는 낮은 자리부터 나오므로 앞에 붙인다 x = Math.floor(x / base); } return s;} const [a, b, n, x, base] = require("fs").readFileSync(0, "utf8").trim().split(/\s+/).map(Number); console.log(gcd(a, b)); // 체: 소수를 찾을 때마다 그 배수를 지운다. 끝까지 안 지워진 수가 소수const isPrime = new Array(n + 1).fill(true);isPrime[0] = isPrime[1] = false;for (let i = 2; i * i <= n; i++) { if (isPrime[i]) { for (let j = i * i; j <= n; j += i) isPrime[j] = false; }}const primes = [];for (let i = 2; i <= n; i++) if (isPrime[i]) primes.push(i);console.log(primes.join(" ")); console.log(toBase(x, base));import { readFileSync } from "node:fs";const DIGITS = "0123456789abcdefghijklmnopqrstuvwxyz"; function gcd(a: number, b: number): number { while (b !== 0) { [a, b] = [b, a % b]; // (a, b)를 (b, a를 b로 나눈 나머지)로 바꾼다 } return a;} function toBase(x: number, base: number): string { if (x === 0) return "0"; let s = ""; while (x > 0) { s = DIGITS[x % base] + s; // 나머지는 낮은 자리부터 나오므로 앞에 붙인다 x = Math.floor(x / base); } return s;} const [a, b, n, x, base] = readFileSync(0, "utf8").trim().split(/\s+/).map(Number); console.log(gcd(a, b)); // 체: 소수를 찾을 때마다 그 배수를 지운다. 끝까지 안 지워진 수가 소수const isPrime: boolean[] = new Array(n + 1).fill(true);isPrime[0] = isPrime[1] = false;for (let i = 2; i * i <= n; i++) { if (isPrime[i]) { for (let j = i * i; j <= n; j += i) isPrime[j] = false; }}const primes: number[] = [];for (let i = 2; i <= n; i++) if (isPrime[i]) primes.push(i);console.log(primes.join(" ")); console.log(toBase(x, base));import java.util.Arrays;import java.util.Scanner; public class Main { static final String DIGITS = "0123456789abcdefghijklmnopqrstuvwxyz"; static int gcd(int a, int b) { while (b != 0) { int r = a % b; // (a, b)를 (b, a를 b로 나눈 나머지)로 바꾼다 a = b; b = r; } return a; } static String toBase(int x, int base) { if (x == 0) return "0"; String s = ""; while (x > 0) { s = DIGITS.charAt(x % base) + s; // 나머지는 낮은 자리부터 나오므로 앞에 붙인다 x /= base; } return s; } public static void main(String[] args) { Scanner in = new Scanner(System.in); int a = in.nextInt(), b = in.nextInt(), n = in.nextInt(), x = in.nextInt(), base = in.nextInt(); System.out.println(gcd(a, b)); // 체: 소수를 찾을 때마다 그 배수를 지운다. 끝까지 안 지워진 수가 소수 boolean[] isPrime = new boolean[n + 1]; Arrays.fill(isPrime, true); isPrime[0] = isPrime[1] = false; for (int i = 2; i * i <= n; i++) { if (isPrime[i]) { for (int j = i * i; j <= n; j += i) isPrime[j] = false; } } StringBuilder primes = new StringBuilder(); for (int i = 2; i <= n; i++) { if (isPrime[i]) primes.append(primes.length() > 0 ? " " : "").append(i); } System.out.println(primes); System.out.println(toBase(x, base)); }}using System;using System.Collections.Generic; class Program{ const string DIGITS = "0123456789abcdefghijklmnopqrstuvwxyz"; static int Gcd(int a, int b) { while (b != 0) { (a, b) = (b, a % b); // (a, b)를 (b, a를 b로 나눈 나머지)로 바꾼다 } return a; } static string ToBase(int x, int b) { if (x == 0) return "0"; string s = ""; while (x > 0) { s = DIGITS[x % b] + s; // 나머지는 낮은 자리부터 나오므로 앞에 붙인다 x /= b; } return s; } static void Main() { string[] ab = Console.ReadLine().Split(); int a = int.Parse(ab[0]), b = int.Parse(ab[1]); int n = int.Parse(Console.ReadLine()); string[] xb = Console.ReadLine().Split(); int x = int.Parse(xb[0]), numBase = int.Parse(xb[1]); // base는 C#의 예약어라 이름을 바꿨다 Console.WriteLine(Gcd(a, b)); // 체: 소수를 찾을 때마다 그 배수를 지운다. 끝까지 안 지워진 수가 소수 bool[] isPrime = new bool[n + 1]; Array.Fill(isPrime, true); isPrime[0] = isPrime[1] = false; for (int i = 2; i * i <= n; i++) { if (isPrime[i]) { for (int j = i * i; j <= n; j += i) isPrime[j] = false; } } List<int> primes = new List<int>(); for (int i = 2; i <= n; i++) if (isPrime[i]) primes.Add(i); Console.WriteLine(string.Join(" ", primes)); Console.WriteLine(ToBase(x, numBase)); }}#include <iostream>#include <string>#include <vector> const std::string DIGITS = "0123456789abcdefghijklmnopqrstuvwxyz"; int gcd(int a, int b) { while (b != 0) { int r = a % b; // (a, b)를 (b, a를 b로 나눈 나머지)로 바꾼다 a = b; b = r; } return a;} std::string toBase(int x, int base) { if (x == 0) return "0"; std::string s = ""; while (x > 0) { s = DIGITS[x % base] + s; // 나머지는 낮은 자리부터 나오므로 앞에 붙인다 x /= base; } return s;} int main() { int a, b, n, x, base; std::cin >> a >> b >> n >> x >> base; std::cout << gcd(a, b) << '\n'; // 체: 소수를 찾을 때마다 그 배수를 지운다. 끝까지 안 지워진 수가 소수 std::vector<bool> isPrime(n + 1, true); isPrime[0] = isPrime[1] = false; for (int i = 2; i * i <= n; i++) { if (isPrime[i]) { for (int j = i * i; j <= n; j += i) isPrime[j] = false; } } std::vector<int> primes; for (int i = 2; i <= n; i++) if (isPrime[i]) primes.push_back(i); for (size_t k = 0; k < primes.size(); k++) std::cout << (k ? " " : "") << primes[k]; std::cout << '\n'; std::cout << toBase(x, base) << '\n';}#include <stdio.h>#include <string.h> const char *DIGITS = "0123456789abcdefghijklmnopqrstuvwxyz";char isPrime[10001]; int gcd(int a, int b) { while (b != 0) { int r = a % b; // (a, b)를 (b, a를 b로 나눈 나머지)로 바꾼다 a = b; b = r; } return a;} // s에 x를 base진법으로 적는다void toBase(int x, int base, char *s) { char buf[40]; int len = 0; if (x == 0) buf[len++] = '0'; while (x > 0) { buf[len++] = DIGITS[x % base]; // 나머지는 낮은 자리부터 나온다 x /= base; } for (int i = 0; i < len; i++) s[i] = buf[len - 1 - i]; // 그래서 거꾸로 옮긴다 s[len] = '\0';} int main(void) { int a, b, n, x, base; scanf("%d %d %d %d %d", &a, &b, &n, &x, &base); printf("%d\n", gcd(a, b)); // 체: 소수를 찾을 때마다 그 배수를 지운다. 끝까지 안 지워진 수가 소수 memset(isPrime, 1, n + 1); isPrime[0] = isPrime[1] = 0; for (int i = 2; i * i <= n; i++) { if (isPrime[i]) { for (int j = i * i; j <= n; j += i) isPrime[j] = 0; } } int count = 0; for (int i = 2; i <= n; i++) { if (isPrime[i]) printf(count++ ? " %d" : "%d", i); } printf("\n"); char s[40]; toBase(x, base, s); printf("%s\n", s); return 0;}DIGITS = "0123456789abcdefghijklmnopqrstuvwxyz"; gcd(a, b){ 동안 (b != 0) { r = a % b; // (a, b)를 (b, a를 b로 나눈 나머지)로 바꾼다 a = b; b = r; } 돌려주기 a;} toBase(x, base){ 만약 (x == 0) { 돌려주기 "0"; } s = ""; 동안 (x > 0) { s = DIGITS[x % base] + s; // 나머지는 낮은 자리부터 나오므로 앞에 붙인다 x = 수학.몫(x, base); } 돌려주기 s;} ab = 콘솔.수들읽기();n = 콘솔.줄읽기().수로변환();xb = 콘솔.수들읽기(); 콘솔.쓰기(gcd(ab[0], ab[1])); // 체: 소수를 찾을 때마다 그 배수를 지운다. 끝까지 안 지워진 수가 소수isPrime = 새목록(n + 1, 참);isPrime[0] = 거짓;isPrime[1] = 거짓;반복 (i = 2; i * i <= n; i++){ 만약 (isPrime[i]) { 반복 (j = i * i; j <= n; j += i) { isPrime[j] = 거짓; } }}primes = [];반복 (i = 2; i <= n; i++){ 만약 (isPrime[i]) { primes.넣기(i); }}콘솔.쓰기(primes); 콘솔.쓰기(toBase(xb[0], xb[1]));84 36 30 255 16
12 2 3 5 7 11 13 17 19 23 29 ff
핵심
- 최대공약수는 두 수를 모두 나누는 가장 큰 수예요.
(a, b)를(b, a % b)로 바꾸는 일을 b가 0이 될 때까지 되풀이하면, 남은 a가 답이에요.
(84, 36) → (36, 12) → (12, 0)이라서 12예요. - 이 방법이 맞는 까닭은, a와 b를 모두 나누는 수는 b와
a % b도 모두 나누고, 거꾸로도 그렇기 때문이에요.
수가 금방 작아져서, 가장 오래 걸리는 수로 해 봐도 1조에 가까운 두 수가 57바퀴에 끝났어요. - 소수는 1과 자기 자신으로만 나누어떨어지는, 1보다 큰 수예요.
n 이하의 소수를 모두 찾을 때는 체를 써요.
2부터 차례로 보다가 아직 안 지워진 수를 만나면 그 수는 소수이고, 그 수의 배수를 모두 지워요. - 배수는
i * i부터 지우면 돼요.
그보다 작은 배수는 더 작은 소수가 이미 지웠어요.
같은 까닭으로 바깥 반복은i * i <= n인 동안만 돌아요. - 수를 다른 진법으로 쓰려면, base로 나눈 나머지를 적고 몫으로 바꾸는 일을 0이 될 때까지 되풀이해요.
나머지는 낮은 자리부터 나와서 앞에 붙여요.
255는 나머지 15(f)·몫 15, 다시 나머지 15(f)·몫 0이라서ff예요. - 예제에는 없지만, 그렇게 쓴 문자열을 거꾸로 수로 읽을 때는 앞 글자부터
x = x * base + 숫자를 되풀이해요.ff는 0 → 15 → 15 × 16 + 15 = 255예요.
10 이상인 숫자는a(10)부터 글자로 써요.
언어별로 쓰는 법
| Python | math.gcd(a, b)가 있어요.2·8·16진법으로 쓰기는 format(255, "b")·"o"·"x"(→ ff), 읽기는 int("ff", 16).다른 진법으로 쓰기는 예제처럼 직접 만들어요. 몫은 // |
|---|---|
| JavaScript | 최대공약수 함수가 없어서 직접 만들어요. 쓰기는 (255).toString(16)(→ ff), 읽기는 parseInt("ff", 16).몫은 Math.floor(x / base) |
| TypeScript | JavaScript와 같아요 |
| Java | int끼리의 최대공약수 함수가 없어서 직접 만들어요(BigInteger에는 gcd가 있어요).쓰기는 Integer.toString(255, 16), 읽기는 Integer.parseInt("ff", 16) |
| C# | BigInteger.GreatestCommonDivisor(a, b)(System.Numerics)가 있지만 int끼리는 직접 만들면 돼요.Convert.ToString(255, 16)과 Convert.ToInt32("ff", 16)은 2·8·10·16진법만 받아요(3을 주면 ArgumentException) |
| C++ | <numeric>의 std::gcd(a, b).읽기는 std::stoi("ff", nullptr, 16), 쓰기는 예제처럼 직접 만들어요.체는 std::vector<bool> |
| C | 최대공약수 함수가 없어서 직접 만들어요. 읽기는 strtol("ff", NULL, 16), 쓰기는 printf의 %x(16진법)·%o(8진법).그 밖의 진법은 숫자를 거꾸로 모은 뒤 뒤집어요 |
| Craykon | 최대공약수·진법 함수가 없어서 모두 직접 만들어요. 몫은 수학.몫(x, base), 숫자 글자는 DIGITS[d], 글자의 값은 DIGITS.자리찾기(글자) |
자주 하는 실수
0을 바꾸니 빈 문자열이 나왔어요
def to_base(x, base):
s = ""
while x > 0:
s = str(x % base) + s
x //= base
return s
print("[" + to_base(0, 2) + "]")→ [] — 대괄호 사이에 아무것도 없어요. x가 0이면 반복이 한 번도 돌지 않아요
x가 0이면 따로 "0"을 돌려줘요.
몫을 /로 구했어요JavaScript · TypeScript · Craykon
let x = 5, s = "";
while (x > 0) {
s = (x % 2) + s;
x = x / 2;
}
console.log(s.length, s.slice(-12));→ 23085 .6251.250.51 — 101이 나와야 하는데 2만 글자가 넘는 문자열이 나왔어요. /가 2.5, 1.25, 0.625…로 소수까지 나눠서 x가 한참 뒤에야 0이 돼요
몫만 구해요.
JavaScript는 Math.floor(x / 2), 크레이콘은 수학.몫(x, 2)예요.
체에서 i * i가 int를 넘었어요Java · C# · C++ · C
int n = 100000;
boolean[] isPrime = new boolean[n + 1];
Arrays.fill(isPrime, true);
for (int i = 2; i <= n; i++)
if (isPrime[i])
for (int j = i * i; j <= n; j += i) isPrime[j] = false;→ ArrayIndexOutOfBoundsException: Index -2146737495 out of bounds for length 100001 — i가 46349일 때 i * i가 약 21억을 넘어 음수가 됐어요. C#은 IndexOutOfRangeException, C·C++는 SIGSEGV로 죽어요
바깥 반복을 i * i <= n인 동안만 돌려요.
n이 10만이면 i는 316까지만 가요.
언어마다 다른 점
내장 함수가 있으면 그것을 쓰되, 없는 언어도 있고 2·8·16진법만 되는 것도 있으니 직접 만드는 법을 알아 두세요.
최소공배수는
먼저 나누고 곱해야 중간 값이 덜 커져요.
내장 함수가 있으면 그것을 쓰되, 없는 언어도 있고 2·8·16진법만 되는 것도 있으니 직접 만드는 법을 알아 두세요.
최소공배수는
a / gcd(a, b) * b예요(84와 36이면 252).먼저 나누고 곱해야 중간 값이 덜 커져요.