{ }codelper
最大公約数全探索再帰P0185

歯車の列を組む

モーターは 1 分に R_in 回まわり、機械は 1 分にちょうど R_out 回まわらなければならない。
その間を歯車の列でつなぐ。
列の一段は歯数が異なる二つの歯車、駆動歯車 a(歯 a 枚)と従動歯車 b(歯 b 枚)からなり、その段を通ると回転数が a/b 倍になる。
使える歯車の歯数が m 種類与えられ、同じ大きさの歯車はいくつでもある(同じ段を何回使ってもよい)。
列は 0 段から 3 段まで使え、出力の回転数は R_in × (各段の a/b をすべて掛けた値)である。
段の順番は結果に関係しないので、同じ段を順番だけ変えた列は同じ列として数える。
出力の回転数がちょうど R_out になる列のうち、最も少ない段数 k と、段が k 個のそのような列の数を空白一つで区切って出力せよ。
0 段の列(段がない)は R_in = R_out のときだけ合い、そのとき答えは "0 1" である。
3 段以下で作れなければ -1 を出力する。

入力
一行目に R_in と R_out、二行目に m、三行目に互いに異なる歯数 m 個。
1 ≤ R_in, R_out ≤ 10,000、1 ≤ m ≤ 8、6 ≤ 歯数 ≤ 200
出力
"k 個数" または -1
例 1 · stdin
640 250
3
16 20 32
stdout
2 1
例 2 · stdin
800 400
4
6 12 24 48
stdout
1 3
main.py
入力(stdin)

実行すると、これらの行が順に input()・標準入力に渡されます(実行中に入力を求めません)。

$ 準備完了

コードを入力して解くにはパソコンで開いてください。モバイルでは問題と解答を見られます。

ほかの言語では?言語別の解答を見る · 8 言語の解答

段は多くても 8 × 7 = 56 通りなので、3 段まで全部選んでも 3 万通りあまりです。
だから再帰で段を一つずつ選びながら全部試します。
次の段をいま選んだ段の番号からだけ選べば(番号が減らないように)、順番だけ違う列を二度数えません。
比較は分数で行います:望む比 R_out/R_in を gcd で既約分数にしておき、段を選ぶたびに分子・分母を掛けてから再び gcd で割ります。
既約分数は一つしかないので、分子どうし・分母どうしが等しいかを見るだけで済みます。
罠 ① 望む比を約分しないと、900/1200 のように約分できる入力で一つも見つかりません。
② 回転数を実数で掛けていき == で比べると、(x * a) / b の丸め誤差のためにぴったりの列を見逃します。
JavaScript・TypeScript・Craykon は数がすべて実数なので特に注意が必要ですが、整数だけを掛け、割り切れるときだけ割れば 2⁵³ まで正確です。
③ 次の段を最初から選び直すと、同じ列を順番ごとに別に数えます。
gcd は Python math.gcd、C++17 std::gcd(<numeric>)があり、Java・C#・C・JavaScript・TypeScript・Craykon はユークリッドの互除法を自分で書きます。
Craykon はここでは 数学.商 の代わりに / を使っても大丈夫です。
gcd で割ればいつも割り切れるからです。

Pythonmain.py · 33 行
from math import gcd  r_in, r_out = map(int, input().split())m = int(input())teeth = list(map(int, input().split()))stages = [(a, b) for a in teeth for b in teeth if a != b]  # (구동 기어, 따라 도는 기어) g = gcd(r_out, r_in)want = (r_out // g, r_in // g)  # 바라는 비 = 출력 / 입력, 기약분수로  def count_trains(start, left, num, den):    """stages[start:]에서 left개를 더 골라(같은 단 여러 번 가능) 비가 want가 되는 열차 수.    고른 단의 번호가 줄지 않게 해서, 순서만 바뀐 열차를 두 번 세지 않는다."""    if left == 0:        return 1 if (num, den) == want else 0    total = 0    for i in range(start, len(stages)):        a, b = stages[i]        n2, d2 = num * a, den * b        c = gcd(n2, d2)        total += count_trains(i, left - 1, n2 // c, d2 // c)  # 늘 기약분수로 줄여 둔다    return total  answer = "-1"for k in range(4):    c = count_trains(0, k, 1, 1)    if c > 0:        answer = f"{k} {c}"        breakprint(answer)
JavaScriptmain.js · 38 行
const data = require("fs").readFileSync(0, "utf8").trim().split(/\s+/).map(Number);const [rIn, rOut, m] = data;const teeth = data.slice(3, 3 + m); function gcd(a, b) {  while (b !== 0) [a, b] = [b, a % b];  return a;} const stages = []; // [구동 기어, 따라 도는 기어]for (const a of teeth) for (const b of teeth) if (a !== b) stages.push([a, b]); const g = gcd(rOut, rIn);const wantNum = rOut / g; // 바라는 비 = 출력 / 입력, 기약분수로const wantDen = rIn / g; // stages[start..]에서 left개를 더 골라(같은 단 여러 번 가능) 비가 바라는 비가 되는 열차 수.// 고른 단의 번호가 줄지 않게 해서, 순서만 바뀐 열차를 두 번 세지 않는다function countTrains(start, left, num, den) {  if (left === 0) return num === wantNum && den === wantDen ? 1 : 0;  let total = 0;  for (let i = start; i < stages.length; i++) {    const [a, b] = stages[i];    const c = gcd(num * a, den * b);    total += countTrains(i, left - 1, (num * a) / c, (den * b) / c); // 늘 기약분수로 줄여 둔다  }  return total;} let answer = "-1";for (let k = 0; k <= 3; k++) {  const c = countTrains(0, k, 1, 1);  if (c > 0) {    answer = `${k} ${c}`;    break;  }}console.log(answer);
TypeScriptmain.ts · 39 行
import { readFileSync } from "node:fs";const data: number[] = readFileSync(0, "utf8").trim().split(/\s+/).map(Number);const [rIn, rOut, m] = data;const teeth: number[] = data.slice(3, 3 + m); function gcd(a: number, b: number): number {  while (b !== 0) [a, b] = [b, a % b];  return a;} const stages: [number, number][] = []; // [구동 기어, 따라 도는 기어]for (const a of teeth) for (const b of teeth) if (a !== b) stages.push([a, b]); const g = gcd(rOut, rIn);const wantNum = rOut / g; // 바라는 비 = 출력 / 입력, 기약분수로const wantDen = rIn / g; // stages[start..]에서 left개를 더 골라(같은 단 여러 번 가능) 비가 바라는 비가 되는 열차 수.// 고른 단의 번호가 줄지 않게 해서, 순서만 바뀐 열차를 두 번 세지 않는다function countTrains(start: number, left: number, num: number, den: number): number {  if (left === 0) return num === wantNum && den === wantDen ? 1 : 0;  let total = 0;  for (let i = start; i < stages.length; i++) {    const [a, b] = stages[i];    const c = gcd(num * a, den * b);    total += countTrains(i, left - 1, (num * a) / c, (den * b) / c); // 늘 기약분수로 줄여 둔다  }  return total;} let answer = "-1";for (let k = 0; k <= 3; k++) {  const c = countTrains(0, k, 1, 1);  if (c > 0) {    answer = `${k} ${c}`;    break;  }}console.log(answer);
JavaMain.java · 51 行
import java.util.*; public class Main {    static List<int[]> stages = new ArrayList<>(); // {구동 기어, 따라 도는 기어}    static long wantNum, wantDen;     static long gcd(long a, long b) {        while (b != 0) {            long t = a % b;            a = b;            b = t;        }        return a;    }     // stages[start..]에서 left개를 더 골라(같은 단 여러 번 가능) 비가 바라는 비가 되는 열차 수.    // 고른 단의 번호가 줄지 않게 해서, 순서만 바뀐 열차를 두 번 세지 않는다    static long countTrains(int start, int left, long num, long den) {        if (left == 0) return num == wantNum && den == wantDen ? 1 : 0;        long total = 0;        for (int i = start; i < stages.size(); i++) {            long n2 = num * stages.get(i)[0], d2 = den * stages.get(i)[1];            long c = gcd(n2, d2);            total += countTrains(i, left - 1, n2 / c, d2 / c); // 늘 기약분수로 줄여 둔다        }        return total;    }     public static void main(String[] args) {        Scanner sc = new Scanner(System.in);        long rIn = sc.nextLong(), rOut = sc.nextLong();        int m = sc.nextInt();        int[] teeth = new int[m];        for (int i = 0; i < m; i++) teeth[i] = sc.nextInt();        for (int a : teeth)            for (int b : teeth)                if (a != b) stages.add(new int[] {a, b});        long g = gcd(rOut, rIn);        wantNum = rOut / g; // 바라는 비 = 출력 / 입력, 기약분수로        wantDen = rIn / g;        String answer = "-1";        for (int k = 0; k <= 3; k++) {            long c = countTrains(0, k, 1, 1);            if (c > 0) {                answer = k + " " + c;                break;            }        }        System.out.println(answer);    }}
C#Main.cs · 54 行
using System;using System.Collections.Generic;using System.Linq; class Program{    static readonly List<(long a, long b)> stages = new List<(long a, long b)>(); // (구동 기어, 따라 도는 기어)    static long wantNum, wantDen;     static long Gcd(long a, long b)    {        while (b != 0) (a, b) = (b, a % b);        return a;    }     // stages[start..]에서 left개를 더 골라(같은 단 여러 번 가능) 비가 바라는 비가 되는 열차 수.    // 고른 단의 번호가 줄지 않게 해서, 순서만 바뀐 열차를 두 번 세지 않는다    static long CountTrains(int start, int left, long num, long den)    {        if (left == 0) return num == wantNum && den == wantDen ? 1 : 0;        long total = 0;        for (int i = start; i < stages.Count; i++)        {            long n2 = num * stages[i].a, d2 = den * stages[i].b;            long c = Gcd(n2, d2);            total += CountTrains(i, left - 1, n2 / c, d2 / c); // 늘 기약분수로 줄여 둔다        }        return total;    }     static void Main()    {        long[] r = Console.ReadLine().Split(' ', StringSplitOptions.RemoveEmptyEntries).Select(long.Parse).ToArray();        Console.ReadLine();        int[] teeth = Console.ReadLine().Split(' ', StringSplitOptions.RemoveEmptyEntries).Select(int.Parse).ToArray();        foreach (int a in teeth)            foreach (int b in teeth)                if (a != b) stages.Add((a, b));        long g = Gcd(r[1], r[0]);        wantNum = r[1] / g; // 바라는 비 = 출력 / 입력, 기약분수로        wantDen = r[0] / g;        string answer = "-1";        for (int k = 0; k <= 3; k++)        {            long c = CountTrains(0, k, 1, 1);            if (c > 0)            {                answer = k + " " + c;                break;            }        }        Console.WriteLine(answer);    }}
C++main.cpp · 42 行
#include <iostream>#include <numeric>#include <utility>#include <vector> std::vector<std::pair<long long, long long>> stages;  // (구동 기어, 따라 도는 기어)long long wantNum, wantDen; // stages[start..]에서 left개를 더 골라(같은 단 여러 번 가능) 비가 바라는 비가 되는 열차 수.// 고른 단의 번호가 줄지 않게 해서, 순서만 바뀐 열차를 두 번 세지 않는다long long countTrains(size_t start, int left, long long num, long long den) {    if (left == 0) return num == wantNum && den == wantDen ? 1 : 0;    long long total = 0;    for (size_t i = start; i < stages.size(); i++) {        long long n2 = num * stages[i].first, d2 = den * stages[i].second;        long long c = std::gcd(n2, d2);        total += countTrains(i, left - 1, n2 / c, d2 / c);  // 늘 기약분수로 줄여 둔다    }    return total;} int main() {    long long rIn, rOut;    int m;    std::cin >> rIn >> rOut >> m;    std::vector<long long> teeth(m);    for (auto& t : teeth) std::cin >> t;    for (long long a : teeth)        for (long long b : teeth)            if (a != b) stages.push_back({a, b});    long long g = std::gcd(rOut, rIn);    wantNum = rOut / g;  // 바라는 비 = 출력 / 입력, 기약분수로    wantDen = rIn / g;    for (int k = 0; k <= 3; k++) {        long long c = countTrains(0, k, 1, 1);        if (c > 0) {            std::cout << k << ' ' << c << '\n';            return 0;        }    }    std::cout << "-1\n";}
Cmain.c · 51 行
#include <stdio.h> static int sa[56], sb[56], nstages = 0; /* 단: 구동 기어 sa, 따라 도는 기어 sb */static long long want_num, want_den; long long gcd(long long a, long long b) {    while (b != 0) {        long long t = a % b;        a = b;        b = t;    }    return a;} /* 단 start번부터 left개를 더 골라(같은 단 여러 번 가능) 비가 바라는 비가 되는 열차 수.   고른 단의 번호가 줄지 않게 해서, 순서만 바뀐 열차를 두 번 세지 않는다 */long long count_trains(int start, int left, long long num, long long den) {    if (left == 0) return num == want_num && den == want_den;    long long total = 0;    for (int i = start; i < nstages; i++) {        long long n2 = num * sa[i], d2 = den * sb[i];        long long c = gcd(n2, d2);        total += count_trains(i, left - 1, n2 / c, d2 / c); /* 늘 기약분수로 줄여 둔다 */    }    return total;} int main(void) {    long long r_in, r_out;    int m, teeth[8];    scanf("%lld %lld %d", &r_in, &r_out, &m);    for (int i = 0; i < m; i++) scanf("%d", &teeth[i]);    for (int i = 0; i < m; i++)        for (int j = 0; j < m; j++)            if (i != j) {                sa[nstages] = teeth[i];                sb[nstages++] = teeth[j];            }    long long g = gcd(r_out, r_in);    want_num = r_out / g; /* 바라는 비 = 출력 / 입력, 기약분수로 */    want_den = r_in / g;    for (int k = 0; k <= 3; k++) {        long long c = count_trains(0, k, 1, 1);        if (c > 0) {            printf("%d %lld\n", k, c);            return 0;        }    }    puts("-1");    return 0;}
Craykonmain.cray · 64 行
최대공약수(a, b){    間 (b != 0)    {        t = a % b;        a = b;        b = t;    }    返す a;} // stages[start..]에서 left개를 더 골라(같은 단 여러 번 가능) 비가 바라는 비가 되는 열차 수.// 고른 단의 번호가 줄지 않게 해서, 순서만 바뀐 열차를 두 번 세지 않는다열차세기(start, left, num, den){    もし (left == 0)    {        もし (num == wantNum && den == wantDen)        {            返す 1;        }        返す 0;    }    total = 0;    繰り返す (s = start; s < stages.個数; s++)    {        n2 = num * stages[s][0];        d2 = den * stages[s][1];        c = 최대공약수(n2, d2);        total += 열차세기(s, left - 1, n2 / c, d2 / c); // 늘 기약분수로 줄여 둔다(나누어떨어져서 / 로 된다)    }    返す total;} r = コンソール.数読み();rIn = r[0];rOut = r[1];m = コンソール.行読み().数に();teeth = コンソール.数読み();stages = []; // [구동 기어, 따라 도는 기어]それぞれ (a の中 teeth){    それぞれ (b の中 teeth)    {        もし (a != b)        {            stages.追加([a, b]);        }    }}g = 최대공약수(rOut, rIn);wantNum = rOut / g; // 바라는 비 = 출력 / 입력, 기약분수로wantDen = rIn / g;answer = "-1";繰り返す (k = 0; k <= 3; k++){    cnt = 열차세기(0, k, 1, 1);    もし (cnt > 0)    {        answer = k + " " + cnt;        抜ける;    }}コンソール.書く(answer);