計算24
グレード 10 開始時間 2020年4月7日火曜日08:55 ディスカウント 0.8 割引時間 2020年5月1日金曜日23:55 遅延提出が可能 番号 閉店時間 2020年5月1日金曜日23:55 カードのデッキには、大小の王に加えて52枚のカードがあり、それぞれ1から13までの4枚のカードがあります。ランダムに与えられた52枚のカードのうち4枚について、+-×÷4回の操作で各数字が1回しか使えないが使えないかを判断し、24枚を計算します。指定された4つの数値は順不同であり、括弧を追加できることに注意してください。
テスト入力 期待される出力 制限時間 メモリ制限 追加プロセス テストケース1
- 1 1 11↵
- 2 3 41↵
- 7 2 11↵
- いいえ↵
- はい↵
- はい↵
1秒 64M 0
1.問題分析
最初に、4桁の組み合わせ演算で考えられるすべての状況を大まかに列挙します。異なる結果をもたらす可能性のある要因は次のとおりです。
- 4桁の並び順:2、3、4、1、2、4、3、1など
- 4桁の演算の順序(括弧の追加方法):(2 + 3)-(4 + 1)や(2 + 3)-4 + 1など
- 4桁の演算(3演算子):(2 + 3)-(4 + 1)や(2 + 3)-(4-1)など
種については大まかにカウントし 、タイムアウトを列挙しません。ただし、多くのカテゴリがあります。ここでの主な目的は、すべての状況を検索する方法を理解することです。主な考慮事項は、バックトラック方法です。剪定か剪定かは関係ありません。
2.アイデア
最初に小さな詳細を見てみましょう。観察により、最初の要素(異なる配置順序)は実際には異なる(2と3の要素が同じである場合)ことがわかりました。たとえば、(2 + 3)-(4 + 1)と(3 + 2)-(1 + 4)。実際には:
加算と乗算はシーケンスとは関係がなく、減算と除算はシーケンスとは関係ありません。
3つの要素を個別に考慮すると、バックトラッキングが非常に複雑になります。また、上記では、要素1と2が別々にリストされている場合、重複する部分があるため、ここでは、要素1と2を組み合わせて、3番目の要素を別々に検討することを説明しました。次に、アルゴリズムの一般的な考え方は次のとおりです:
- 4つの数値から2つの数値を選択して演算を実行します。演算の結果はxですが、計算する数値は3つあります(xと演算に関係しない2つの数値)
- 3つの数値から2つの数値を選択して演算を実行します。演算の結果はxですが、計算する数値は2つあります(xと演算に関係しない数値)。
- 残りの2つの数値を選択して、最終的な結果yを得る操作を実行します
- y = 24の場合、つまり、この選択の結果はOKであり、出力はyesです。それ以外の場合は、再選択します(すべてが選択され、結果が24で結果が見つからない場合)。
1、2因子の合併に関する考慮事項の一部: 3つの選択プロセスは、1因子と2因子の違いによって引き起こされるすべての状況を含む、1因子と2因子全体の合併です。
3つの要因に関する考慮事項:選択した2つの数値(無秩序)に対して毎回操作を実行するプロセスに対応します。ただし、ここでは4種類の操作だけでなく、6種類の操作も列挙する必要があります。2つの数値に対する順序付けされていない演算のすべての結果について、減算と除算は2回列挙する必要があります。
3.アルゴリズムの実装
ここでの実装は、2つのループのネストとフラグ配列マークを巧みに使用して、選択プロセスをシミュレートしているため、冗長でなく、リークもありません。理解するのはより困難です。ループ変数iとjはそれぞれ、検索する2つの数値を表し、マークを外す必要があります(つまり、計算されていません。そうでない場合、このビットの選択はスキップされます)。注:ここで選択した2つの数値の計算結果は、iの位置に保存され、計算が続行されます。そして、j位置のみがマークされます。つまり、2つを取り出して結果を保存します。
ループ本体に入った後、2つの数値に対して6つの演算が列挙されます。
(実際、バックトラッキングアルゴリズムの各ステップの値は、この記事の2にリストされているアルゴリズムのアイデアに対応するステップの数です。)
この質問は少し理解するのが難しいですが、私のコードを組み合わせて消化すると、すぐに理解できるはずです〜ACコードを以下に掲載します:
//
// Created by A on 2020/4/29.
//
#include <cstdio>
#include <cmath>
#include <cstring>
#define DIF 0.0000001
bool flag[5] = {false}; //标记该位是否被计算过
double num[5] = {0}; //每一位的值
bool Calc24(int step) {
if (step == 4) {
for (int i = 0; i < 4; i++) //结果可能在1、2、3、4位中(取决于运算的顺序)
if (!flag[i] && fabs(num[i] - 24) <= DIF) //剩余位上(结果)的值几乎为24
return true;
return false;
}
for (int i = 1; i <= 4; i++)
if (!flag[i])
for (int j = i + 1; j <= 4; j++) {
if (!flag[j]) {
double temp1 = num[i], temp2 = num[j]; //分别取出待运算的两位
flag[j] = true; //由于计算结果储存在第 i 位可以继续参与运算,而第 j 位不再继续
/* 分别搜索:两种数的 6 种运算 */
num[i] = temp1 + temp2;
if (Calc24(step + 1))
return true;
num[i] = temp1 - temp2;
if (Calc24(step + 1))
return true;
num[i] = temp1 * temp2;
if (Calc24(step + 1))
return true;
if (temp2 != 0) { //排除分母为零的情况
num[i] = temp1 / temp2;
if (Calc24(step + 1))
return true;
}
num[i] = temp2 - temp1;
if (Calc24(step + 1))
return true;
if (temp1 != 0) { //排除分母为零的情况
num[i] = temp2 / temp1;
if (Calc24(step + 1))
return true;
}
/* 还原,便于回溯 */
num[i] = temp1;
flag[j] = false;
}
}
return false; //一直没有出口
}
int main() {
while (EOF != scanf("%lf %lf %lf %lf", &num[1], &num[2], &num[3], &num[4])) {
memset(flag, false, 5 * sizeof(bool)); //一定注意初始化
if (Calc24(1))
printf("yes\n");
else
printf("no\n");
}
}
4.エラーが発生しやすいポイントの概要
-
フラグ配列は各ループで初期化する必要があります!そうでない場合、最後の結果が保持され、今回はエラーが発生します
-
2つの除算演算を列挙するときは、除数が0の場合をスキップしてください。そうでなければ、それは
-
判定結果がstep = 4の場合、結果ビットが24かどうか(現時点では結果ビットのフラグのみが偽)かどうかの判定が必要であり、それ以外の場合は中間結果が24であればイエスと判定されることがあります。これを理解していない場合は、次の使用例を試すことができます。3 4 2 3.正しい結果は「いいえ」です。このため、私は最後のユースケースでした
-
中央の値は中央に格納され、小数があるため、num配列はdoubleに格納する必要があります
-
結果が24に等しいかどうかの決定は、浮動小数点数の決定です。浮動小数点数の計算は完全に正確にすることはできず、一部のエラーを排除する必要があるためです。したがって、24と直接判断しないでください。ただし、24との違いはわずかです。
よかったら気に入ってください〜