仕事算の答えが整数になる問題の作成方法

[仕事算](https://ja.wikipedia.org/wiki/仕事算)やその関連問題は分数の計算が必要になるので、答えが分数（や小数）になりやすい。~~採点の効率化のため~~答えが整数になるような問題を作成したいが、どうすればそのような値を探し出せるだろうか。

## 例題
作りたい問題はこんな感じ。小学生向けの文章題以外にも様々なバリエーションができる。

答えは本記事の最後に。

### 仕事算
ある廊下の掃除をひとりで行うと、Aさんは**12分**、Bさんは**15分**、Cさんは**20分**かかる。この3人が一緒に掃除した場合は何分で完了するか。

### 電気回路
値が **100Ω**, **120Ω**, **150Ω** の3つの抵抗器を並列に繋いだときの合成抵抗はいくらか。

（コンデンサの場合は直列にすることで同様の問題を作れる）

### 追いつき算
兄弟が公園を同じルートで何周かジョギングする。1周に要する時間は、兄は**10分**、弟は**14分**である。兄弟が出会うのは何分毎になるか。

（[惑星の会合周期](https://eco.mtk.nao.ac.jp/koyomi/wiki/CFC7C0B12FB2F1B9E7.html)などへ応用できる）

### 幾何学
仕事算という括りではないが、計算方法が同じようになる問題が幾何学にも色々ある。

#### レンズの公式
ある凸レンズの左側 **24cm** の距離に光源を置いたところ、レンズの右側 **40cm** の位置に倒立の実像が現れた。このレンズの焦点距離はいくらか。

（符号をうまく取り扱えば、虚像や凹レンズにも対応できる）

#### 直角三角錐の高さ
角 O が全て 90° の直角三角錐 OABC がある。辺の長さは OA = **90mm**, OB = **150mm**, OC = **200mm** である。頂点 O から平面 ABC に垂線をおろすとき、その長さはいくつか。

（[デカルト・グアの定理](https://en.wikipedia.org/wiki/de_Gua's_theorem)[^degua]と相補的であり、他の次元への適用も見通しが良い）

[^degua]: 三平方の定理（ピタゴラスの定理）からの類推で、日本語では「四平方の定理」とみんな名付けている様子。（数論の定理とは無関係）

### その他
[調和平均](https://ja.wikipedia.org/wiki/調和平均)を参照。

## 基本的な解法
### 手計算
これらの問題では**仕事の能率の和**を考えればいい。能率というのは**時間の逆数**で考えられる[^speed]ため、各仕事時間を R<sub>1</sub>, R<sub>2</sub>, … 、協力したときを R で表せば、次の式が成り立つ。

```math
\frac{1}{R} = \frac{1}{R_1} + \frac{1}{R_2} + \cdots
```

全 R<sub>i</sub> が整数である場合の計算テクニックとして、右辺で分数の足し算を避けるために、全 R<sub>i</sub> の公倍数（仮に V とする）を両辺に掛ける方法がある。とはいえ、そうすれば右辺は整数になるものの、その総和が先ほどの公倍数 V を割り切れなければ、答えは分数となってしまう。

```math
R = \frac{V}{\frac{V}{R_1} + \frac{V}{R_2} + \cdots}
```

[^speed]: 「能率」とは、言い方を変えれば「仕事の速度」。速度なので量を時間で割ることになる。

### プログラム
Qiitaなので一応プログラムでも計算する。Rubyでサクッと解いてみる。

```shell-session:terminal
$ ruby -v
ruby 2.6.0rc1 (2018-12-06 trunk 66253) [x86_64-linux]
$ ruby -e 'puts (1 / ARGV.sum { |s| 1 / Complex(s) }).real' -- 12 15 20
5
```

わざわざ複素数クラス `Complex` で計算しているのは、内部で実数クラス（ `Integer`, `Rational`, `Float` ）を適切に選んで処理してくれるから。上記の例では引数に整数のみを与えているが、分数や小数を表す文字列を与えても計算できる。詳細はRubyの込み入った話になってしまうので、[別記事](/HMMNRST/items/d54996a9b8cf6b97917f)で説明する。

### 二項演算と結合法則
3つ以上の仕事の能率の和を考える場合、実は二項演算さえ定義してあれば、逐次演算していくことで求められる。

```math
R_i \star R_j := \frac{R_i R_j}{R_i + R_j} \\
\downarrow \\
R = R_1 \star R_2 \star \cdots
```

結合法則を満たすのでどこから計算を実行してもいいし、さらに交換法則も満たすので順序を入れ替えてもいい。

```math
\begin{align}
100 \star 120 \star 150
&= (100 \star 150) \star 120 \\
&= 60 \star 120 \\
&= 40
\end{align}
```

例題で色々出した通り結構便利な演算なので、[電卓を自作した際に入れておいた](/HMMNRST/items/97d930a80d63d9838531#逆数和の逆数)ことがある。足し算や掛け算と同じ要領で、何個の演算でも連続して使える。

## 本題：問題の作成
### 考え方
値を直接探すのではなく、**先に能率を決めてしまう**ことで必ず整数を見つけられる。

0. 例えば個々の能率を **2** と **3** とする。
0. すると合計の能率は 2 + 3 = **5** である。
0. これらの値の**最小公倍数**を計算する。合計の能率も含めるのがポイント。 lcm(2,3,5) = **30**
0. 最小公倍数をそれぞれの能率で割れば、仕事算の問題と答えが整数で得られる。この例では問題が 30/2 = 15 と 30/3 = 10 、答えが 30/5 = 6 である。

最小公倍数を使ったため最も小さな値が求まっているが、これら全ての値を等倍しても構わない。例えば4倍すれば、 60, 40 → 24 という問題を作れる。

個々の能率は3個以上でもいいし、マイナスでもいい。ただし最小公倍数が0になる組み合わせはダメ。

### 良い値の探索
能率に小さな値を選んだからといって問題の値が小さくきれいになるわけではない。例えば 2 と 5 を選ぶと、和は 7 のため最小公倍数は 70 、よって問題は 35, 14 → 10 となる。7の倍数であることが気持ち悪い（※個人的な意見）。3つ以上の数となると意図通りの問題を作るのは難しい。

なので可能な値を列挙してしまい、そこから良さそうなものを目視で探すことにする。

```ruby:harmonic_add_list.rb
M =  3 # 協力の数
N = 50 # 探索する能率の最大値

list = [*1..N].combination(M) # 同じ値があってもよければ #repeated_combination を使う
	.select { |ary| ary.inject(0, :gcd) == 1 } # 公約数がある組合せはスキップ
	.map do |ary|
		s = ary.sum
		m = ary.inject(s, :lcm)
		ary.map { |e| m / e } << (m / s) # 能率から仕事へ変換（配列に追加した値は答え）
	end

# 最大仕事時間が小さく、能率の開き（比）が小さく、協力時間が長い順に列挙
list.sort_by { |ary| [ary[0], ary[0].fdiv(ary[-2]), -ary[-1]] }
	.take(30).each(&method(:p))
```

```text:output
[6, 3, 2, 1]
[15, 12, 10, 4]
[15, 10, 6, 3]
[15, 10, 3, 2]
[18, 12, 9, 4]
[18, 9, 3, 2]
[20, 15, 12, 5]
[20, 12, 5, 3]
[20, 5, 4, 2]
[24, 8, 6, 3]
[24, 8, 3, 2]
[28, 24, 21, 8]
[28, 21, 12, 6]
[28, 14, 7, 4]
[28, 21, 6, 4]
[28, 21, 4, 3]
[30, 18, 9, 5]
[30, 24, 8, 5]
[30, 10, 5, 3]
[30, 20, 4, 3]
[33, 22, 11, 6]
[35, 21, 15, 7]
[35, 15, 14, 6]
[35, 14, 10, 5]
[36, 28, 21, 9]
[36, 30, 20, 9]
[36, 18, 4, 3]
[40, 30, 15, 8]
[40, 35, 14, 8]
[40, 20, 8, 5]
```

能率の最大値 `N` を100に増やしてもoutputに変化は無かったので、仕事算に使える小さな候補は上記くらいだろう。あとは作りたい問題に合わせて、値の比率を考慮して選んだり[^ratio]、それを適当な倍率で大きくすればいい。

[^ratio]: 誰かひとりだけ仕事が10倍も遅いとかいう設定は、問題として良くないだろうということ。

## 例題の答え
* 仕事算：5分
* 電気回路：40Ω
* 追いつき算：35分（※弟の仕事をマイナスと考える）
* 幾何学
	* レンズの公式：15cm
	* 直角三角錐の高さ：72mm（※全ての数を2乗して考える）