C++
 Computer >> コンピューター >  >> プログラミング >> C++

C++で解く「水とジョッキ」問題(Water Jug Problem)――最大公約数を使った計量判定アルゴリズム

容量がそれぞれ x リットルy リットルの2つの容器(ジョッキ)があるとします。水は無限に供給されており、この2つの容器だけを使ってちょうど z リットルの水を計量できるかどうかを判定するのが、この問題の目的です。z リットルが計量可能であるためには、操作の終了時点で、どちらか一方または両方の容器内の水の合計が正確に z リットルになっている必要があります。

許容される操作

この問題では、次の3種類の操作のみが許可されています。

  • どちらかの容器に水を満杯まで注ぐ。
  • どちらかの容器の水をすべて捨てて空にする。
  • 片方の容器からもう片方の容器へ水を移す。ただし、移し先が満杯になるか、移し元が空になるまで続けます。

具体例

例えば、x = 2、y = 5、z = 4 の場合、答えは true になります。実際、5リットルの容器を満タンにして2リットルの容器へ移す操作を繰り返すことで、最終的にちょうど4リットルを計量できます。

解法のアプローチ

この問題は、数学的な性質を利用すると非常にシンプルに解けます。手順は以下の通りです。

  • x + y < z の場合:両方の容器を合わせても z リットルに届かないため、false を返します。
  • x == z、y == z、または x + y == z の場合:そのまま計量できるため、true を返します。
  • 上記以外の場合:z が x と y の最大公約数(gcd)で割り切れるなら true、そうでなければ false を返します。

なぜ最大公約数で判定できるのか?

この判定が成り立つ背景には、ベズーの等式(Bézout's identity)があります。これは「ax + by = z を満たす整数 a, b が存在するのは、z が gcd(x, y) の倍数であるとき、かつそのときに限る」という定理です。容器への注水・移し替え操作は、実質的に x と y を何度か足したり引いたりする操作に相当するため、z が gcd(x, y) の倍数であれば必ず計量可能になります。

C++での実装例

以下の実装を見ると、理解がより深まるでしょう。

#include <bits/stdc++.h>
using namespace std;
class Solution {
   public:
   bool canMeasureWater(int x, int y, int z) {
      if(x + y < z) return false;
      if(x == z || y == z || x + y == z) return true;
      return z % __gcd(x, y) == 0;
   }
};
main(){
   Solution ob;
   cout << (ob.canMeasureWater(3,5,4));
}

入力

3
5
4

出力

1

出力が 1(true)となっており、3リットルと5リットルの容器で4リットルが計量可能であることが確認できます。

計算量について

この解法は、ユークリッドの互除法による gcd 計算が中心となるため、時間計算量は O(log(min(x, y))) ときわめて高速です。シミュレーションで全パターンを試す方法(BFSなど)と比べても、はるかに効率的なアプローチと言えます。

  1. C++のfmax()関数とfmin()関数の使い方を徹底解説

    この記事では、C++におけるfmax()およびfmin()関数の使い方について詳しく解説します。これらの関数は、cmathヘッダーファイルに定義されており、数値比較を簡単に行うための標準ライブラリ関数です。fmax()とfmin()の基本fmax()とfmin()は、それぞれ最大値と最小値を返す関数です。引数として指定できるのは、float、double、long double型の値で、2つの引数を受け取って比較を行います。また、引数の型が異なる場合(例えばfloatとdoubleを比較する場合など)でも、暗黙的に型変換(キャスト)が行われた上で適切な結果が返されるため、異なる浮動小数点型同士

  2. C++のdelete演算子とfree()関数の違いと使い方を徹底解説

    C++やC言語で動的に確保したメモリを解放する方法として、主にdelete演算子とfree()関数の2つがあります。本記事では、それぞれの構文、使い方、そして実際のコード例を通して、両者の違いをわかりやすく解説します。 delete演算子とは delete演算子は、new演算子によって動的に確保されたメモリを解放するために使用されます。プログラマは、この演算子を使って自分で作成したポインタ変数が指すメモリを自由に解放できます。 C++におけるdelete演算子の基本構文は以下の通りです。 delete ポインタ変数名; また、new[]で確保した配列形式のメモリブロックを解放する場合は、次の