C++で材料を無駄なく使い切るハンバーガーの個数を求める方法
問題の概要
2つの整数 tomatoSlices(トマトのスライス数)と cheeseSlices(チーズのスライス数)が与えられます。これらは、次の2種類のバーガーを作るための材料です。
- ジャンボバーガー: トマトのスライス4枚とチーズのスライス1枚を使用
- スモールバーガー: トマトのスライス2枚とチーズのスライス1枚を使用
与えられた材料をすべて使い切り、トマトのスライスとチーズのスライスの残りがどちらも0になるように、ジャンボバーガーとスモールバーガーの個数 [total_jumbo, total_small] を求めます。もし材料を余らせずに使い切ることが不可能な場合は、空の配列 [] を返します。
例として、tomatoSlices = 16、cheeseSlices = 7 が入力された場合、出力は [1, 6] になります。これは、ジャンボバーガー1個とスモールバーガー6個を作ると、トマトのスライスが 4×1 + 2×6 = 16 枚、チーズのスライスが 1 + 6 = 7 枚となり、すべての材料をちょうど使い切れることを示しています。
解法のアプローチ
この問題は、連立一次方程式として捉えることができます。ジャンボバーガーの個数を j、スモールバーガーの個数を s とすると、次の2式が成り立ちます。
- 4j + 2s = tomatoSlices
- j + s = cheeseSlices
この方程式を解くために、以下の手順に従います。
- 結果を格納するための配列 ans を用意します。
- トマトのスライス数が奇数、または cheese > tomato/2、または tomato > 4×cheese のいずれかに該当する場合は、材料をちょうど使い切ることが不可能なため、空の ans をそのまま返します。
- x := (4 × cheese − tomato) / 2 を計算します(スモールバーガーの個数)。
- y := (tomato − 2×x) / 4 を計算します(ジャンボバーガーの個数)。
- y、x の順に ans に挿入します。
- ans を返します。
条件チェックの理由
なぜこれらの条件で不可能性を判定できるのでしょうか。理由は次のとおりです。
- 必要なトマトのスライス数は 4j + 2s = 2(2j + s) と表せるため、必ず偶数でなければなりません。
- トマトの必要枚数の半分は 2j + s、チーズの必要枚数は j + s なので、cheese ≤ tomato/2 が成立する必要があります。
- トマトのスライス数は最大でも 4(j + s) = 4×cheese 枚であるため、tomato ≤ 4×cheese が成立する必要があります。
これらの条件をすべて満たしていれば、解は必ず一意に存在することが保証されます。
C++による実装例
それでは、理解を深めるために実際の実装を見てみましょう。
例
#include <bits/stdc++.h>
using namespace std;
void print_vector(vector<auto> v){
cout << "[";
for(int i = 0; i<v.size(); i++){
cout << v[i] << ", ";
}
cout << "]"<<endl;
}
class Solution {
public:
vector<int> numOfBurgers(int t, int c) {
vector <int> ans;
if(t % 2 != 0 || c > t/2 || t > c*4)return ans;
int x = (4 * c - t) / 2;
int y = ( t - (2 * x) )/ 4;
ans.push_back(y);
ans.push_back(x);
return ans;
}
};
main(){
Solution ob;
print_vector(ob.numOfBurgers(16,7));
}入力
16 7
出力
[1, 6]
このように、連立方程式を立てて条件判定を行うことで、与えられた材料から無駄なく作れるバーガーの組み合わせを、O(1) の計算量で効率的に求めることができます。
-
C++で数値Nが基数b表記において1から始まるかどうかを判定する方法
本記事では、数値Nと基数bが与えられたとき、「Nを基数bで表記すると先頭の桁が1になるかどうか」を判定する方法をC++で解説します。 まず具体例を見てみましょう。数値6を2進数で表すと「110」であり、先頭は1です。4進数で表すと「12(4)」となり、これも先頭が1です。このように、同じ数でも基数によって表記が変わり、先頭の桁が1になる場合とならない場合があります。 判定の考え方(数学的な背景) 数値Nを基数bで表すと、m+1桁の数列 dm dm-1 … d0 になり、次の関係が成り立ちます。 dm × bm + dm-1 × bm-1 + … + d0 × b0 = N ここで、先頭の桁
-
【C++入門】変数と実数を使って配列を初期化する方法を徹底解説
配列とは配列とは、連続したメモリ領域上に同じ型の要素をまとめて格納したデータ構造です。配列内で最も小さいアドレスは先頭の要素に対応し、最も大きいアドレスは末尾の要素に対応します。また、配列のインデックス(添字)は 0 から始まり、最大で「配列サイズ - 1」までとなります。C++では、配列を初期化する際に変数だけでなく、実数(数値リテラル)を直接指定することもできます。以下のサンプルプログラムで、その具体的な方法を見ていきましょう。サンプルコード#include <iostream>using namespace std;int main() {