アルゴリズム · 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" を返します。
篩で 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 までしか進みません。
商を / で求めた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)、Craykon は 数学.商(x, 2) です。
言語ごとの違い
組み込みの関数があればそれを使いますが、ない言語もあり、2・8・16進数しか扱えないものもあるので、自分で作る方法を知っておきましょう。
最小公倍数は
先に割ってから掛けると、途中の値が大きくなりにくくなります。
組み込みの関数があればそれを使いますが、ない言語もあり、2・8・16進数しか扱えないものもあるので、自分で作る方法を知っておきましょう。
最小公倍数は
a / gcd(a, b) * b です(84 と 36 なら 252)。先に割ってから掛けると、途中の値が大きくなりにくくなります。