├── LICENSE ├── README.md ├── errata.md └── solvers ├── chap1_brute_force ├── 1_1_ten_puzzle_solver.cpp ├── 1_2_komachi_solver.cpp └── 1_3_mushikui_solver.cpp ├── chap2_graph ├── 2_1_sudoku_maker.cpp ├── 2_1_sudoku_solver.cpp ├── 2_2_fukumen_maker_by_listup.cpp ├── 2_2_fukumen_maker_by_wildcard.cpp ├── 2_2_fukumen_solver.cpp ├── 2_3_maze_solver.cpp └── 2_3_oil_solver.cpp └── chap3_advanced ├── 3_1_15puzzle_solver.cpp ├── 3_2_othello_solver.cpp ├── 3_3_edit_distance_solver.cpp └── 3_4_domino_solver.cpp /LICENSE: -------------------------------------------------------------------------------- 1 | Creative Commons Legal Code 2 | 3 | CC0 1.0 Universal 4 | 5 | CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE 6 | LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN 7 | ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS 8 | INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES 9 | REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS 10 | PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM 11 | THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED 12 | HEREUNDER. 13 | 14 | Statement of Purpose 15 | 16 | The laws of most jurisdictions throughout the world automatically confer 17 | exclusive Copyright and Related Rights (defined below) upon the creator 18 | and subsequent owner(s) (each and all, an "owner") of an original work of 19 | authorship and/or a database (each, a "Work"). 20 | 21 | Certain owners wish to permanently relinquish those rights to a Work for 22 | the purpose of contributing to a commons of creative, cultural and 23 | scientific works ("Commons") that the public can reliably and without fear 24 | of later claims of infringement build upon, modify, incorporate in other 25 | works, reuse and redistribute as freely as possible in any form whatsoever 26 | and for any purposes, including without limitation commercial purposes. 27 | These owners may contribute to the Commons to promote the ideal of a free 28 | culture and the further production of creative, cultural and scientific 29 | works, or to gain reputation or greater distribution for their Work in 30 | part through the use and efforts of others. 31 | 32 | For these and/or other purposes and motivations, and without any 33 | expectation of additional consideration or compensation, the person 34 | associating CC0 with a Work (the "Affirmer"), to the extent that he or she 35 | is an owner of Copyright and Related Rights in the Work, voluntarily 36 | elects to apply CC0 to the Work and publicly distribute the Work under its 37 | terms, with knowledge of his or her Copyright and Related Rights in the 38 | Work and the meaning and intended legal effect of CC0 on those rights. 39 | 40 | 1. Copyright and Related Rights. A Work made available under CC0 may be 41 | protected by copyright and related or neighboring rights ("Copyright and 42 | Related Rights"). Copyright and Related Rights include, but are not 43 | limited to, the following: 44 | 45 | i. the right to reproduce, adapt, distribute, perform, display, 46 | communicate, and translate a Work; 47 | ii. moral rights retained by the original author(s) and/or performer(s); 48 | iii. publicity and privacy rights pertaining to a person's image or 49 | likeness depicted in a Work; 50 | iv. rights protecting against unfair competition in regards to a Work, 51 | subject to the limitations in paragraph 4(a), below; 52 | v. rights protecting the extraction, dissemination, use and reuse of data 53 | in a Work; 54 | vi. database rights (such as those arising under Directive 96/9/EC of the 55 | European Parliament and of the Council of 11 March 1996 on the legal 56 | protection of databases, and under any national implementation 57 | thereof, including any amended or successor version of such 58 | directive); and 59 | vii. other similar, equivalent or corresponding rights throughout the 60 | world based on applicable law or treaty, and any national 61 | implementations thereof. 62 | 63 | 2. Waiver. To the greatest extent permitted by, but not in contravention 64 | of, applicable law, Affirmer hereby overtly, fully, permanently, 65 | irrevocably and unconditionally waives, abandons, and surrenders all of 66 | Affirmer's Copyright and Related Rights and associated claims and causes 67 | of action, whether now known or unknown (including existing as well as 68 | future claims and causes of action), in the Work (i) in all territories 69 | worldwide, (ii) for the maximum duration provided by applicable law or 70 | treaty (including future time extensions), (iii) in any current or future 71 | medium and for any number of copies, and (iv) for any purpose whatsoever, 72 | including without limitation commercial, advertising or promotional 73 | purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each 74 | member of the public at large and to the detriment of Affirmer's heirs and 75 | successors, fully intending that such Waiver shall not be subject to 76 | revocation, rescission, cancellation, termination, or any other legal or 77 | equitable action to disrupt the quiet enjoyment of the Work by the public 78 | as contemplated by Affirmer's express Statement of Purpose. 79 | 80 | 3. Public License Fallback. Should any part of the Waiver for any reason 81 | be judged legally invalid or ineffective under applicable law, then the 82 | Waiver shall be preserved to the maximum extent permitted taking into 83 | account Affirmer's express Statement of Purpose. In addition, to the 84 | extent the Waiver is so judged Affirmer hereby grants to each affected 85 | person a royalty-free, non transferable, non sublicensable, non exclusive, 86 | irrevocable and unconditional license to exercise Affirmer's Copyright and 87 | Related Rights in the Work (i) in all territories worldwide, (ii) for the 88 | maximum duration provided by applicable law or treaty (including future 89 | time extensions), (iii) in any current or future medium and for any number 90 | of copies, and (iv) for any purpose whatsoever, including without 91 | limitation commercial, advertising or promotional purposes (the 92 | "License"). The License shall be deemed effective as of the date CC0 was 93 | applied by Affirmer to the Work. Should any part of the License for any 94 | reason be judged legally invalid or ineffective under applicable law, such 95 | partial invalidity or ineffectiveness shall not invalidate the remainder 96 | of the License, and in such case Affirmer hereby affirms that he or she 97 | will not (i) exercise any of his or her remaining Copyright and Related 98 | Rights in the Work or (ii) assert any associated claims and causes of 99 | action with respect to the Work, in either case contrary to Affirmer's 100 | express Statement of Purpose. 101 | 102 | 4. Limitations and Disclaimers. 103 | 104 | a. No trademark or patent rights held by Affirmer are waived, abandoned, 105 | surrendered, licensed or otherwise affected by this document. 106 | b. Affirmer offers the Work as-is and makes no representations or 107 | warranties of any kind concerning the Work, express, implied, 108 | statutory or otherwise, including without limitation warranties of 109 | title, merchantability, fitness for a particular purpose, non 110 | infringement, or the absence of latent or other defects, accuracy, or 111 | the present or absence of errors, whether or not discoverable, all to 112 | the greatest extent permissible under applicable law. 113 | c. Affirmer disclaims responsibility for clearing rights of other persons 114 | that may apply to the Work or any use thereof, including without 115 | limitation any person's Copyright and Related Rights in the Work. 116 | Further, Affirmer disclaims responsibility for obtaining any necessary 117 | consents, permissions or other rights required for any use of the 118 | Work. 119 | d. Affirmer understands and acknowledges that Creative Commons is not a 120 | party to this document and has no duty or obligation with respect to 121 | this CC0 or use of the Work. 122 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 概要 2 | 3 | 本レポジトリは、拙著『パズルで鍛えるアルゴリズム力』の補足資料です。以下の内容を掲載しています。 4 | 5 | - 本書で掲載しているパズルソルバー:solvers フォルダ以下 6 | - 正誤表:errata.md 7 | 8 |   9 | 10 | # 本書で使用する言語 11 | 12 | 本書では、パズルを解くアルゴリズムを実装するための言語として C++ を採用します。ただし基本的な機能のみを用いているため、ほかの多くの言語で同様の処理を実現できます。 13 | 14 |   15 | 16 | # License 17 | 18 | These codes are licensed under CC0. 19 | 20 | [![CC0](http://i.creativecommons.org/p/zero/1.0/88x31.png "CC0")](http://creativecommons.org/publicdomain/zero/1.0/deed.ja) -------------------------------------------------------------------------------- /errata.md: -------------------------------------------------------------------------------- 1 | # 正誤表 2 | 3 | 現時点で判明している誤植などを掲載していきます。 4 | 5 |   6 | 7 | | 該当ページ | 該当箇所 | 誤 | 正 | 備考 | 対応 | 8 | | ---------- | ----------------------------- | ----------------------------------------------- | ------------------------------------------------------------ | ------------------------------------------------------------ | ---------- | 9 | | p. 19 | リスト 1-2 | | 関数 `decode_poland()` を、以下ファイルのように変更します。https://github.com/drken1215/book_puzzle_algorithm/blob/master/solvers/chap1_brute_force/1_1_ten_puzzle_solver.cpp | 引き算を表す演算子「-」の後側にもカッコが必要でした。 | | 10 | | P. 73 | リスト 1-17 の 78 行目 | `if (vec.empty() && add == 0) continue;` | `if (vec.size() == multiplicand_.size() - 1 && add == 0) continue;` | | | 11 | | p. 89 | リスト 2-1 11 行目 | dfs(v); | dfs(潜った先の v); | forward(v) 中に v が書き換えられるイメージでしたが、dfs(v) のままですと同じ頂点を再帰呼び出ししているように見えるため、修正します。 | | 12 | | p. 254 | 図 3-49 の caption | 頂点 (1, 1) | 頂点 (1, 2) | | | 13 | | p. 258 | 注10 | \|T(N)\| > c\|f(N)\| | \|T(N)\| <= c\|f(N)\| | | | 14 | | p. 267 | コラム「テトロミノ」 | 本文中の「図J」と「図K」 | それぞれ「図H」と「図I」 | | | 15 | 16 |   17 | -------------------------------------------------------------------------------- /solvers/chap1_brute_force/1_1_ten_puzzle_solver.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | using namespace std; 8 | 9 | // 逆ポーランド記法の計算式を計算する 10 | double calc_poland(const string& exp) { 11 | // 計算のための配列 12 | vector space; 13 | 14 | // 逆ポーランド記法 exp の各文字 c を順に見る 15 | for (char c : exp) { 16 | if (c >= '0' && c <= '9') { 17 | // c が数字を表す文字の場合、 18 | // '7' のような文字リテラルを 7 のような数値に変換する 19 | int add = c - '0'; 20 | 21 | // 配列の末尾に挿入する 22 | space.push_back(add); 23 | } else { 24 | // c が演算子の場合、末尾から 2 つの数を取り出す 25 | double second = space.back(); 26 | space.pop_back(); 27 | double first = space.back(); 28 | space.pop_back(); 29 | 30 | // 演算の実施結果を配列の末尾に挿入する 31 | if (c == '+') 32 | space.push_back(first + second); 33 | else if (c == '-') 34 | space.push_back(first - second); 35 | else if (c == '*') 36 | space.push_back(first * second); 37 | else 38 | space.push_back(first / second); 39 | } 40 | } 41 | // 配列の末尾に残っている値を返す 42 | return space.back(); 43 | } 44 | 45 | // 逆ポーランド記法の計算式から、通常の計算式を復元する 46 | string decode_poland(const string& exp) { 47 | // 通常の計算式の復元のための配列 48 | vector space; 49 | 50 | // 逆ポーランド記法 exp の各文字 c を順に見る 51 | for (char c : exp) { 52 | if (c >= '0' && c <= '9') { 53 | // 数字を表す文字 c を文字列に変換して配列の末尾に挿入する 54 | space.push_back({c}); 55 | } else { 56 | // c が演算子の場合、末尾から 2 つの計算式を取り出す 57 | string second = space.back(); 58 | space.pop_back(); 59 | string first = space.back(); 60 | space.pop_back(); 61 | 62 | // 演算子が「*」「/」で、 63 | // 演算子の前の式が「+」「-」を含むとき括弧をつける 64 | if (first.find('+') != string::npos || 65 | first.find('-') != string::npos) { 66 | if (c == '*' || c == '/') { 67 | first = "(" + first + ")"; 68 | } 69 | } 70 | 71 | // 演算子が「-」「*」「/」で、 72 | // 演算子の後の式が「+」「-」を含むとき括弧をつける 73 | if (second.find('+') != string::npos || 74 | second.find('-') != string::npos) { 75 | if (c == '-' || c == '*' || c == '/') { 76 | second = "(" + second + ")"; 77 | } 78 | } 79 | 80 | // 演算子をもとに復元した計算式を配列の末尾に挿入する 81 | if (c == '+') 82 | space.push_back(first + " + " + second); 83 | else if (c == '-') 84 | space.push_back(first + " - " + second); 85 | else if (c == '*') 86 | space.push_back(first + " * " + second); 87 | else 88 | space.push_back(first + " / " + second); 89 | } 90 | } 91 | return space.back(); 92 | } 93 | 94 | // テンパズルソルバー 95 | // val: 4 つの数を格納した配列、target: 作りたい数 96 | vector solve(vector val, int target) { 97 | // 答えを表す計算式を格納する配列 98 | vector res; 99 | 100 | // 逆ポーランド記法の計算式 exp を試すための関数オブジェクト 101 | const double EPS = 1e-9; // 十分小さい値 102 | auto check = [&](const string& exp) -> void { 103 | // 計算結果と作りたい数との差が十分小さいとき、一致とみなす 104 | if (abs(calc_poland(exp) - target) < EPS) 105 | res.push_back(decode_poland(exp)); 106 | }; 107 | 108 | // 4 つの数 val の並び替えを順に試していく 109 | sort(val.begin(), val.end()); // val を辞書順最小にする 110 | do { 111 | // 4 つの数字を連結してできる文字列 fours を作る 112 | string fours = ""; 113 | for (int v : val) fours += to_string(v); 114 | 115 | // 4^3 = 64 通りの演算子の組合せを試していく 116 | const string ops = "+-*/"; // 4 つの演算子 117 | for (char op1 : ops) { 118 | for (char op2 : ops) { 119 | for (char op3 : ops) { 120 | // まず、パターン "xxxoooo" を作る 121 | string exp = fours + op1 + op2 + op3; 122 | 123 | // パターン "xxxxooo" を試す 124 | check(exp); 125 | 126 | // パターン "xxxoxoo" を試す 127 | swap(exp[3], exp[4]), check(exp); 128 | 129 | // パターン "xxxooxo" を試す 130 | swap(exp[4], exp[5]), check(exp); 131 | 132 | // パターン "xxoxoxo" を試す 133 | swap(exp[2], exp[3]), check(exp); 134 | 135 | // パターン "xxoxxoo" を試す 136 | swap(exp[4], exp[5]), check(exp); 137 | } 138 | } 139 | } 140 | } while (next_permutation(val.begin(), val.end())); 141 | return res; 142 | } 143 | 144 | int main() { 145 | // 4 つの数と、作りたい数の入力 146 | vector val(4); // 4 つの数 147 | int target; // 作りたい数 148 | for (int i = 0; i < 4; ++i) { 149 | cout << i + 1 << " th number: "; 150 | cin >> val[i]; 151 | } 152 | cout << "target number: "; 153 | cin >> target; 154 | 155 | // テンパズルを解く 156 | const vector& res = solve(val, target); 157 | 158 | // 出力 159 | for (const string& exp : res) 160 | cout << exp << " = " << target << endl; 161 | } 162 | 163 | -------------------------------------------------------------------------------- /solvers/chap1_brute_force/1_2_komachi_solver.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | using namespace std; 7 | 8 | // 演算子の種類 9 | const int EMPTY = 0; 10 | const int PLUS = 1; 11 | const int MINUS = 2; 12 | const int MUL = 3; 13 | const int DIV = 4; 14 | 15 | // 作りたい数 16 | const int TARGET = 100; 17 | 18 | // □ に入れた演算子のうち、空白部分を処理する関数 19 | pair, vector> calc_empty(const vector& signs) { 20 | // 空白部分の処理後の計算式を表すデータ 21 | vector new_vals; // 数値 22 | vector new_signs; // 演算子 23 | 24 | // 途中経過の値 (小町算の最初の値は 1 である) 25 | double val = 1; 26 | 27 | // 演算子を順に見ていく 28 | for (int i = 0; i < signs.size(); ++i) { 29 | // 新たな数値 (i = 0, ..., 7 のとき add = 2, ..., 9) 30 | double add = i + 2; 31 | 32 | if (signs[i] == EMPTY) { 33 | // 空白の場合は数値を連結する (ex: 23 * 10 + 4 = 234) 34 | val = val * 10 + add; 35 | } else { 36 | // そうでない場合は、数値と演算子の組を新たに記録する 37 | new_vals.push_back(val); 38 | new_signs.push_back(signs[i]); 39 | 40 | // □ の直後の新たな値を val に記録する 41 | val = add; 42 | } 43 | } 44 | 45 | // 最後の値を push して、答えを返す 46 | new_vals.push_back(val); 47 | return make_pair(new_vals, new_signs); 48 | } 49 | 50 | // 掛け算、割り算の部分を計算する関数 51 | pair, vector> 52 | calc_mul_div(const vector& vals, const vector& signs) { 53 | // 掛け算、割り算部分の処理後の計算式を表すデータ 54 | vector new_vals; // 数値 55 | vector new_signs; // 演算子 56 | 57 | // 途中経過の値 58 | double val = vals[0]; 59 | 60 | // 演算子を順に見ていく 61 | for (int i = 0; i < signs.size(); ++i) { 62 | // 新たな数値 63 | double add = vals[i + 1]; 64 | 65 | if (signs[i] == MUL) { 66 | val *= add; 67 | } else if (signs[i] == DIV) { 68 | val /= add; 69 | } else { 70 | // 掛け算、割り算でない場合は、数値と演算子の組を新たに記録する 71 | new_vals.push_back(val); 72 | new_signs.push_back(signs[i]); 73 | 74 | // □ の直後の新たな値を val に記録する 75 | val = add; 76 | } 77 | } 78 | 79 | // 最後の値を push して、答えを返す 80 | new_vals.push_back(val); 81 | return make_pair(new_vals, new_signs); 82 | } 83 | 84 | // 足し算、引き算の部分を計算する関数 85 | double calc_plus_minus 86 | (const vector& vals, const vector& signs) { 87 | // 答えを表す変数 88 | double res = vals[0]; 89 | 90 | // 演算子を順に見ていく 91 | for (int i = 0; i < signs.size(); ++i) { 92 | // 新たな数値 93 | double add = vals[i + 1]; 94 | 95 | if (signs[i] == PLUS) res += add; 96 | else if (signs[i] == MINUS) res -= add; 97 | } 98 | return res; 99 | } 100 | 101 | // sign: 符号の入れ方を表す 8 次元ベクトル 102 | double calc(const vector& signs) { 103 | // Step 1: 空白の連結 104 | pair, vector> step1 = calc_empty(signs); 105 | 106 | // Step 2: 掛け算、割り算 107 | pair, vector> step2 108 | = calc_mul_div(step1.first, step1.second); 109 | 110 | // Step 3: 足し算、引き算 111 | return calc_plus_minus(step2.first, step2.second); 112 | } 113 | 114 | // 数式を再現する 115 | string decode(const vector& sign) { 116 | string res = "1"; 117 | for (int i = 0; i < sign.size(); ++i) { 118 | // 演算子を追加 119 | if (sign[i] == PLUS) res += " + "; 120 | else if (sign[i] == MINUS) res += " - "; 121 | else if (sign[i] == MUL) res += " * "; 122 | else if (sign[i] == DIV) res += " / "; 123 | 124 | // 数値を追加 125 | res += to_string(i + 2); 126 | } 127 | return res; 128 | } 129 | 130 | // 再帰関数 131 | void rec(const vector& vec, vector& res) { 132 | // 終端条件: vec のサイズが 8 133 | if (vec.size() == 8) { 134 | // 計算結果と 100 との差が十分小さいとき一致とみなす 135 | const double EPS = 1e-9; // 十分小さい値 136 | if (abs(calc(vec) - TARGET) < EPS) 137 | res.push_back(decode(vec)); 138 | 139 | // 再帰処理を終える 140 | return; 141 | } 142 | 143 | // vec の末尾に 5 値を順に挿入して試す 144 | for (int add = 0; add < 5; ++add) { 145 | // vec を新しい配列 vec2 にコピー 146 | vector vec2 = vec; 147 | 148 | // vec2 の末尾に add を追加 149 | vec2.push_back(add); 150 | 151 | // 再帰呼び出し 152 | rec(vec2, res); 153 | } 154 | } 155 | 156 | int main() { 157 | // 再帰関数の処理用に、空の配列を定義 158 | vector vec; 159 | 160 | // 再帰的に求める 161 | vector res; // 解を格納する配列 162 | rec(vec, res); 163 | 164 | // 答えを出力する 165 | cout << "The number of solutions: " << res.size() << endl; 166 | for (const string& str : res) 167 | cout << str << " = " << TARGET << endl; 168 | } 169 | -------------------------------------------------------------------------------- /solvers/chap1_brute_force/1_3_mushikui_solver.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | using namespace std; 6 | 7 | // 整数 val が文字列 str に整合するか 8 | bool is_valid(int64_t val, const string& str) { 9 | // 整数 val を文字列に変換する 10 | string sval = to_string(val); 11 | 12 | // 整数 val の桁数が文字列 str の長さに一致する必要がある 13 | if (sval.size() != str.size()) return false; 14 | 15 | // 1 文字ずつ整合性を確認する 16 | for (int i = 0; i < sval.size(); ++i) { 17 | // □ については不問 18 | if (str[i] == '*') continue; 19 | 20 | // 一致しない場合は false 21 | if (sval[i] != str[i]) return false; 22 | } 23 | return true; 24 | } 25 | 26 | // 数字 v を文字列 str の右から k 番めに当てはめても整合するか 27 | bool is_valid_sub(int v, int k, const string& str) { 28 | // 右から k 番めの文字を取得する 29 | char c = str[str.size() - 1 - k]; 30 | 31 | // 虫食いの場合は問題ない 32 | if (c == '*') return true; 33 | 34 | // たとえば c = '8' のとき、real_val = 8 になる 35 | int real_val = c - '0'; 36 | 37 | // 数値が一致するかどうかを判定する 38 | return (v == real_val); 39 | } 40 | 41 | // □ に入れた数字から、整数全体を復元する 42 | int64_t decode(const vector& vec) { 43 | int64_t res = 0; // 答え 44 | int64_t order = 1; // 1, 10, 100, ... 45 | for (int v : vec) { 46 | res += order * v; 47 | order *= 10; 48 | } 49 | return res; 50 | } 51 | 52 | // 虫食算ソルバーを管理するクラス 53 | class Mushikuizan { 54 | private: 55 | // 被乗数、乗数、積 56 | string multiplicand_, multiplier_, product_; 57 | 58 | // 部分積 59 | vector middle_; 60 | 61 | // 答えを表す配列 (被乗数と乗数のペアを格納) 62 | vector> res_; 63 | 64 | public: 65 | // コンストラクタ 66 | Mushikuizan(const string& multiplicand, 67 | const string& multiplier, 68 | const string& product, 69 | const vector& middle) : 70 | multiplicand_(multiplicand), multiplier_(multiplier), 71 | product_(product), middle_(middle) { 72 | } 73 | 74 | // 乗数の □ に数字を入れていく再帰関数 75 | // plicand: 被乗数に入れた数字、vec: 乗数の □ に数字を入れた結果 76 | void rec_plier(int64_t plicand, vector& vec) { 77 | // 終端条件: 乗数の □ のすべてに数字が入ったとき 78 | if (vec.size() == multiplier_.size()) { 79 | // 乗数を求める 80 | int64_t plier = decode(vec); 81 | 82 | // 積の整合性を確認する 83 | if (!is_valid(plicand * plier, product_)) return; 84 | 85 | // 答えを格納する 86 | res_.emplace_back(plicand, plier); 87 | return; 88 | } 89 | 90 | // 新しい □ に数字を入れる (乗数に 0 は入らないものとする) 91 | for (int add = 1; add <= 9; ++add) { 92 | // すでに数字が入っていて矛盾している場合はスキップ 93 | if (!is_valid_sub(add, vec.size(), multiplier_)) 94 | continue; 95 | 96 | // □ に数字 add を入れて整合性を確認する 97 | if (!is_valid(plicand * add, middle_[vec.size()])) 98 | continue; 99 | 100 | // vec に数字 add を加えて再帰呼び出し 101 | vec.push_back(add); 102 | rec_plier(plicand, vec); 103 | vec.pop_back(); 104 | } 105 | } 106 | 107 | // 被乗数の □ に数字を入れていく関数 108 | // vec: 被乗数の □ に数字を入れた結果 109 | void rec_plicand(vector& vec) { 110 | // 終端条件: 被乗数の □ のすべてに数字が入ったとき 111 | if (vec.size() == multiplicand_.size()) { 112 | // 今度は乗数の □ に数字を入れていく 113 | vector vec2; 114 | rec_plier(decode(vec), vec2); 115 | return; 116 | } 117 | 118 | // 新しい □ に数字を入れる 119 | for (int add = 0; add <= 9; ++add) { 120 | // 左端に 0 は入れられない 121 | if (vec.size() == multiplicand_.size() - 1 && add == 0) continue; 122 | 123 | // すでに数字が入っていて矛盾している場合はスキップ 124 | if (!is_valid_sub(add, vec.size(), multiplicand_)) 125 | continue; 126 | 127 | // vec に数値 add を加えて再帰呼び出し 128 | vec.push_back(add); 129 | rec_plicand(vec); 130 | vec.pop_back(); 131 | } 132 | } 133 | 134 | // 虫食算を解く (被乗数と乗数のペアを格納した配列を返す) 135 | vector> solve() { 136 | // 答えを表す配列を空にする 137 | res_.clear(); 138 | 139 | // 被乗数の □ の方から数字を入れていく 140 | vector vec; // 空の配列 141 | rec_plicand(vec); 142 | return res_; 143 | } 144 | }; 145 | 146 | int main() { 147 | // 入力 148 | cout << "Mushikuizan Input: " << endl; 149 | int A, B; // 被乗数、乗数の桁数 150 | cin >> A >> B; 151 | string hijou, jou, seki; // 被乗数、乗数、積 152 | vector middle(B); // 中間部 153 | cin >> hijou >> jou; 154 | for (int i = 0; i < B; ++i) cin >> middle[i]; 155 | cin >> seki; 156 | 157 | // 再帰的に解く 158 | Mushikuizan mu(hijou, jou, seki, middle); 159 | const vector>& res = mu.solve(); 160 | 161 | // 解を出力 162 | cout << "The num of solutions: " << res.size() << endl; 163 | for (int i = 0; i < res.size(); ++i) { 164 | cout << i << " th solution: " 165 | << res[i].first << " * " << res[i].second 166 | << " = " << res[i].first * res[i].second << endl; 167 | } 168 | } 169 | 170 | -------------------------------------------------------------------------------- /solvers/chap2_graph/2_1_sudoku_maker.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | using namespace std; 8 | 9 | // 盤面を二次元ベクトルで表す 10 | using Field = vector>; 11 | 12 | // 数独を解くためのクラス 13 | class Sudoku { 14 | private: 15 | Field field_; 16 | 17 | public: 18 | // コンストラクタ (未確定マスの値を -1 で表す) 19 | Sudoku() : field_(9, vector(9, -1)) { 20 | } 21 | 22 | // filed データを返す 23 | const Field& get() const { 24 | return field_; 25 | } 26 | 27 | // マス (x, y) に数字 val を入れる 28 | void put(int x, int y, int val) { 29 | field_[x][y] = val; 30 | } 31 | 32 | // マス (x, y) の数字を削除する 33 | void reset(int x, int y) { 34 | field_[x][y] = -1; 35 | } 36 | 37 | // 入力データを受け取る 38 | void input() { 39 | for (int x = 0; x < 9; ++x) { 40 | string line; 41 | cin >> line; 42 | 43 | // マス (x, y) を処理する 44 | for (int y = 0; y < 9; ++y) { 45 | // 空マスの場合は -1 のままにしておく 46 | if (line[y] == '*') continue; 47 | 48 | // 数字に変換してマス (x, y) に入れる 49 | int val = line[y] - '0'; 50 | put(x, y, val); 51 | } 52 | } 53 | } 54 | 55 | // 空きマスを探索する (存在しない場合は false を返す) 56 | bool find_empty(int& x, int& y); 57 | 58 | // マス (x, y) に入れられる数字の集合を返す 59 | vector find_choices(int x, int y); 60 | }; 61 | 62 | // 空きマスを探索する (存在しない場合は false を返す) 63 | bool Sudoku::find_empty(int& x, int& y) { 64 | for (x = 0; x < 9; ++x) 65 | for (y = 0; y < 9; ++y) 66 | if (field_[x][y] == -1) 67 | return true; 68 | return false; 69 | } 70 | 71 | // マス (x, y) に入れられる数字の集合を返す 72 | vector Sudoku::find_choices(int x, int y) { 73 | // マス (x, y) と同じ行、列、ブロックにある数字を求める 74 | set cannot_use; 75 | 76 | // 同じ行に含まれる数字を除外する 77 | for (int i = 0; i < 9; ++i) 78 | if (field_[x][i] != -1) 79 | cannot_use.insert(field_[x][i]); 80 | 81 | // 同じ列に含まれる数字を除外する 82 | for (int i = 0; i < 9; ++i) 83 | if (field_[i][y] != -1) 84 | cannot_use.insert(field_[i][y]); 85 | 86 | // 同じブロックに含まれる数字を除外する 87 | int x2 = x / 3 * 3, y2 = y / 3 * 3; // 同じブロックの左上のマス 88 | for (int i = x2; i < x2 + 3; ++i) 89 | for (int j = y2; j < y2 + 3; ++j) 90 | if (field_[i][j] != -1) 91 | cannot_use.insert(field_[i][j]); 92 | 93 | // マス (x, y) に入れられる数字からなる配列を求める 94 | vector res; 95 | for (int val = 1; val <= 9; ++val) 96 | if (!cannot_use.count(val)) 97 | res.push_back(val); 98 | return res; 99 | } 100 | 101 | // 数独を解くための再帰関数 102 | void dfs(Sudoku& board, vector& res, bool all = true) { 103 | // フラグ all が false のときは、解を 1 個求めたら抜ける 104 | if (!all && !res.empty()) return; 105 | 106 | // 空きマスを探す (存在しない場合は終端条件) 107 | int x, y; 108 | if (!board.find_empty(x, y)) { 109 | // 解に追加 110 | res.push_back(board.get()); 111 | return; 112 | } 113 | 114 | // マス (x, y) に入れられる数字の集合を求める 115 | const vector& can_use = board.find_choices(x, y); 116 | 117 | // 空きマス (x, y) に数字を順に入れていく 118 | for (int val : can_use) { 119 | board.put(x, y, val); // マス (x, y) に数字 val を入れる 120 | dfs(board, res, all); // 再帰呼び出し 121 | board.reset(x, y); // マス (x, y) の数字を削除 122 | } 123 | } 124 | 125 | // 数独を解く 126 | vector solve(Sudoku& board, bool all = true) { 127 | // 答えを格納した配列 128 | vector res; 129 | 130 | // 再帰関数を呼び出す 131 | dfs(board, res, all); 132 | return res; 133 | } 134 | 135 | // 数独の盤面を出力する関数 136 | void print(const Sudoku& board) { 137 | const Field& field = board.get(); 138 | for (int x = 0; x < 9; ++x) { 139 | for (int y = 0; y < 9; ++y) { 140 | if (field[x][y] == -1) 141 | cout << "*"; 142 | else 143 | cout << field[x][y]; 144 | } 145 | cout << endl; 146 | } 147 | } 148 | 149 | int main() { 150 | // 乱数のシードを固定する 151 | mt19937 rand_src(1); 152 | 153 | // 数独を入力する 154 | Sudoku board; 155 | vector> cells; // ピンク色マスの集合 156 | for (int x = 0; x < 9; ++x) { 157 | string line; 158 | cin >> line; 159 | for (int y = 0; y < 9; ++y) { 160 | if (line[y] >= '0' && line[y] <= '9') { 161 | int val = line[y] - '0'; 162 | board.put(x, y, val); 163 | } else if (line[y] == 'o') { 164 | cells.emplace_back(x, y); 165 | } 166 | } 167 | } 168 | 169 | // 数独を最初に解き、解の 1 つを board に入力して初期解とする 170 | vector res = solve(board, false); // 解を 1 つだけ求める 171 | for (pair p : cells) 172 | board.put(p.first, p.second, res[0][p.first][p.second]); 173 | 174 | // 初期盤面を改めて解く 175 | res = solve(board); 176 | int score = res.size(); // 解の個数を初期スコアとする 177 | cout << "initial problem: " << score << " sols" << endl; 178 | print(board); 179 | 180 | // 山登り法の実施 (10000 回実行してもダメなら打ち切る) 181 | for (int iter = 0; iter < 10000; ++iter) { 182 | // 一意解問題が見つかったら探索を打ち切る 183 | if (score == 1) break; 184 | 185 | // 新たな盤面を作る 186 | Sudoku board2 = board; 187 | 188 | // 「ピンク色マスの数字をランダムに変更する」を 2 回実施する 189 | for (int con = 0; con < 2; ++con) { 190 | // ピンク色マスをランダムに選び、一旦数字を削除する 191 | int id = rand_src() % cells.size(); 192 | int x = cells[id].first, y = cells[id].second; 193 | board2.reset(x, y); 194 | 195 | // 選んだマスに入れられる数字をランダムに選んで入れる 196 | vector can = board2.find_choices(x, y); 197 | int val = can[rand_src() % can.size()]; 198 | board2.put(x, y, val); 199 | } 200 | 201 | // 新たな盤面で数独を解く 202 | res = solve(board2); 203 | int new_score = res.size(); // 新たな盤面の解の個数 204 | 205 | // 改善するならば置き換える 206 | if (new_score < score && new_score != 0) { 207 | cout << iter << ": " << score << " sols -> " 208 | << new_score << " sols" << endl; 209 | board = board2; // 盤面を更新 210 | score = new_score; // スコアを更新 211 | print(board); // 改善後の盤面を出力 212 | } 213 | } 214 | 215 | // 最終問題を出力 216 | cout << "final problem: " << endl; 217 | print(board); 218 | } 219 | 220 | -------------------------------------------------------------------------------- /solvers/chap2_graph/2_1_sudoku_solver.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | using namespace std; 6 | 7 | // 盤面を二次元ベクトルで表す 8 | using Field = vector>; 9 | 10 | // 数独を解くためのクラス 11 | class Sudoku { 12 | private: 13 | Field field_; 14 | 15 | public: 16 | // コンストラクタ (未確定マスの値を -1 で表す) 17 | Sudoku() : field_(9, vector(9, -1)) { 18 | } 19 | 20 | // filed データを返す 21 | const Field& get() { 22 | return field_; 23 | } 24 | 25 | // マス (x, y) に数字 val を入れる 26 | void put(int x, int y, int val) { 27 | field_[x][y] = val; 28 | } 29 | 30 | // マス (x, y) の数字を削除する 31 | void reset(int x, int y) { 32 | field_[x][y] = -1; 33 | } 34 | 35 | // 入力データを受け取る 36 | void input() { 37 | for (int x = 0; x < 9; ++x) { 38 | string line; 39 | cin >> line; 40 | 41 | // マス (x, y) を処理する 42 | for (int y = 0; y < 9; ++y) { 43 | // 空マスの場合は -1 のままにしておく 44 | if (line[y] == '*') continue; 45 | 46 | // 数字に変換してマス (x, y) に入れる 47 | int val = line[y] - '0'; 48 | put(x, y, val); 49 | } 50 | } 51 | } 52 | 53 | // 空きマスを探索する (存在しない場合は false を返す) 54 | bool find_empty(int& x, int& y); 55 | 56 | // マス (x, y) に入れられる数字の集合を返す 57 | vector find_choices(int x, int y); 58 | }; 59 | 60 | // 空きマスを探索する (存在しない場合は false を返す) 61 | bool Sudoku::find_empty(int& x, int& y) { 62 | for (x = 0; x < 9; ++x) 63 | for (y = 0; y < 9; ++y) 64 | if (field_[x][y] == -1) 65 | return true; 66 | return false; 67 | } 68 | 69 | // マス (x, y) に入れられる数字の集合を返す 70 | vector Sudoku::find_choices(int x, int y) { 71 | // マス (x, y) と同じ行、列、ブロックにある数字を求める 72 | set cannot_use; 73 | 74 | // 同じ行に含まれる数字を除外する 75 | for (int i = 0; i < 9; ++i) 76 | if (field_[x][i] != -1) 77 | cannot_use.insert(field_[x][i]); 78 | 79 | // 同じ列に含まれる数字を除外する 80 | for (int i = 0; i < 9; ++i) 81 | if (field_[i][y] != -1) 82 | cannot_use.insert(field_[i][y]); 83 | 84 | // 同じブロックに含まれる数字を除外する 85 | int x2 = x / 3 * 3, y2 = y / 3 * 3; // 同じブロックの左上のマス 86 | for (int i = x2; i < x2 + 3; ++i) 87 | for (int j = y2; j < y2 + 3; ++j) 88 | if (field_[i][j] != -1) 89 | cannot_use.insert(field_[i][j]); 90 | 91 | // マス (x, y) に入れられる数字からなる配列を求める 92 | vector res; 93 | for (int val = 1; val <= 9; ++val) 94 | if (!cannot_use.count(val)) 95 | res.push_back(val); 96 | return res; 97 | } 98 | 99 | // 数独を解くための再帰関数 100 | void dfs(Sudoku& board, vector& res, bool all = true) { 101 | // フラグ all が false のときは、解を 1 個求めたら抜ける 102 | if (!all && !res.empty()) return; 103 | 104 | // 空きマスを探す (存在しない場合は終端条件) 105 | int x, y; 106 | if (!board.find_empty(x, y)) { 107 | // 解に追加 108 | res.push_back(board.get()); 109 | return; 110 | } 111 | 112 | // マス (x, y) に入れられる数字の集合を求める 113 | const vector& can_use = board.find_choices(x, y); 114 | 115 | // 空きマス (x, y) に数字を順に入れていく 116 | for (int val : can_use) { 117 | board.put(x, y, val); // マス (x, y) に数字 val を入れる 118 | dfs(board, res, all); // 再帰呼び出し 119 | board.reset(x, y); // マス (x, y) の数字を削除 120 | } 121 | } 122 | 123 | // 数独を解く 124 | vector solve(Sudoku& board, bool all = true) { 125 | // 答えを格納した配列 126 | vector res; 127 | 128 | // 再帰関数を呼び出す 129 | dfs(board, res, all); 130 | return res; 131 | } 132 | 133 | int main() { 134 | // 数独を入力する 135 | cout << "Sudoku Input: " << endl; 136 | Sudoku board; 137 | board.input(); 138 | 139 | // 数独を解く 140 | vector res = solve(board); 141 | 142 | // 解を出力する 143 | if (res.size() == 0) { 144 | cout << "No solutions." << endl; 145 | } else if (res.size() > 1) { 146 | cout << "More than one solution." << endl; 147 | } else { 148 | const Field& answer = res[0]; 149 | for (int x = 0; x < 9; ++x) { 150 | for (int y = 0; y < 9; ++y) 151 | cout << answer[x][y] << " "; 152 | cout << endl; 153 | } 154 | } 155 | } 156 | 157 | -------------------------------------------------------------------------------- /solvers/chap2_graph/2_2_fukumen_maker_by_listup.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | using namespace std; 7 | 8 | // 覆面算を解くためのクラス 9 | class Fukumenzan { 10 | private: 11 | // 覆面算の文字列を格納した配列 12 | vector problem_; 13 | 14 | // 計算のための変数 15 | vector> board_; // 文字に数字を入れていく過程 16 | set used_; // すでに使用した数字の集合 17 | 18 | // 未確定であることを表す数字 19 | const int NOTIN = -1; 20 | 21 | public: 22 | // コンストラクタ 23 | Fukumenzan(const vector& input) : problem_(input) { } 24 | 25 | // 覆面算を解く過程を初期化する 26 | void init() { 27 | // 文字に数字を入れていく盤面 board 全体を NOTIN で初期化する 28 | board_.resize(problem_.size()); 29 | for (int i = 0; i < problem_.size(); ++i) { 30 | board_[i].assign(problem_[i].size(), NOTIN); 31 | } 32 | used_.clear(); 33 | } 34 | 35 | // 覆面算全体の行数を返す 36 | int get_size() const { return problem_.size(); } 37 | 38 | // 覆面算全体の桁数 (最下行の桁数) を返す 39 | int get_digit() const { return problem_.back().size(); } 40 | 41 | // 覆面算の上から row 行めの桁数を返す 42 | int get_digit(int row) const { return problem_[row].size(); } 43 | 44 | // 数字 val がすでに使われているかどうかを返す 45 | bool is_used(int val) const { return used_.count(val); } 46 | 47 | // 覆面算の上から row 行め、右から digit 桁めに入れた数字を返す 48 | int get_val(int row, int digit) const { 49 | // digit 桁めが row 行めの文字列からはみ出す場合は 0 とする 50 | if (digit >= get_digit(row)) return 0; 51 | return board_[row][get_digit(row) - 1 - digit]; 52 | } 53 | 54 | // 覆面算の上から row 行め、右から digit 桁めの文字を返す 55 | char get_char(int row, int digit) const { 56 | return problem_[row][get_digit(row) - 1 - digit]; 57 | } 58 | 59 | // 覆面算の計算結果を出力する 60 | void print() const { 61 | for (const vector& vec : board_) { 62 | for (int v : vec) cout << v; 63 | cout << endl; 64 | } 65 | } 66 | 67 | // row 行め、右から digit 桁めの文字に数値 val を入れる 68 | void set_val(int row, int digit, int val); 69 | 70 | // row 行め、右から digit 桁めの文字に入れていた数字を削除する 71 | void reset_val(int row, int digit, int val); 72 | 73 | // 覆面文字に入れた数字によって計算が合うかどうかを確認する 74 | bool is_valid(); 75 | }; 76 | 77 | // row 行め、右から digit 桁めの文字に数字 val を入れる 78 | void Fukumenzan::set_val(int row, int digit, int val) { 79 | // 該当の文字を求める 80 | char c = get_char(row, digit); 81 | 82 | // 文字が c であるすべてのマスに数字 val を入れる 83 | for (int r = 0; r < get_size(); ++r) { 84 | for (int d = 0; d < get_digit(r); ++d) { 85 | if (problem_[r][d] == c) board_[r][d] = val; 86 | } 87 | } 88 | used_.insert(val); // 数字 val を使用済みとする 89 | } 90 | 91 | // row 行め、右から digit 桁めの文字に入れていた数字を削除する 92 | void Fukumenzan::reset_val(int row, int digit, int val) { 93 | // 該当の文字を求める 94 | char c = get_char(row, digit); 95 | 96 | // 文字が c であるすべてのマスの数字を削除する 97 | for (int r = 0; r < get_size(); ++r) { 98 | for (int d = 0; d < get_digit(r); ++d) { 99 | if (problem_[r][d] == c) board_[r][d] = -1; 100 | } 101 | } 102 | used_.erase(val); // 数字 val を今後使用可能とする 103 | } 104 | 105 | // 覆面文字に入れた数字によって計算が合うかどうかを確認する 106 | bool Fukumenzan::is_valid() { 107 | // 最上桁に 0 があってはいけない 108 | for (const vector& val : board_) { 109 | if (val[0] == 0) return false; 110 | } 111 | 112 | // 右から 0 桁めから順に計算していく 113 | int kuriagari = 0; // 繰り上がりの値 114 | for (int digit = 0; digit < get_digit(); ++digit) { 115 | // 右から digit 桁めの、最下行以外の行の和を計算する 116 | int sum = 0; 117 | for (int row = 0; row < get_size(); ++row) { 118 | // まだ数字が入っていない部分に来たらその場で終了する 119 | if (get_val(row, digit) == NOTIN) return true; 120 | 121 | // 最下段以外を足す 122 | if (row != get_size() - 1) sum += get_val(row, digit); 123 | } 124 | 125 | // 前の桁からの繰り上がりを足して、新たな繰り上がりを計算する 126 | sum += kuriagari; 127 | kuriagari = sum / 10; 128 | 129 | // 最下行に来るべき数字 (sum % 10) が合っているかを確かめる 130 | if (sum % 10 != get_val(get_size() - 1, digit)) return false; 131 | } 132 | 133 | // 最後に繰り上がりが残ってはいけない 134 | return (kuriagari == 0); 135 | } 136 | 137 | // 深さ優先探索のための再帰関数 138 | // 上から row 行め、右から digit 桁めに数字を入れようとする 139 | void dfs(Fukumenzan& fu, int row, int digit, vector& res) { 140 | // 終端条件: すべての文字に数字が入ったとき 141 | if (row == 0 && digit == fu.get_digit()) { 142 | res.push_back(fu); // 答えに格納する 143 | return; 144 | } 145 | 146 | // 再帰呼び出しのための次のマスを求める 147 | int next_row = row + 1, next_digit = digit; 148 | if (next_row == fu.get_size()) { 149 | next_row = 0, next_digit = digit + 1; 150 | } 151 | 152 | // すでに数字が入っているかどうかで場合分け 153 | if (fu.get_val(row, digit) != -1) { 154 | dfs(fu, next_row, next_digit, res); 155 | } else { 156 | for (int val = 0; val <= 9; ++val) { 157 | // すでに使用済みの数字は使えない 158 | if (fu.is_used(val)) continue; 159 | 160 | fu.set_val(row, digit, val); // 文字に数字を入れる 161 | if (fu.is_valid()) { 162 | dfs(fu, next_row, next_digit, res); 163 | } 164 | fu.reset_val(row, digit, val); // 文字から数字を削除する 165 | } 166 | } 167 | } 168 | 169 | // 覆面算を解く関数 170 | vector solve(Fukumenzan& fu) { 171 | // 和の桁数が最大でない場合は解なしなので、空の配列を返す 172 | for (int i = 0; i < fu.get_size() - 1; ++i) { 173 | if (fu.get_digit(i) > fu.get_digit()) { 174 | return vector(); 175 | } 176 | } 177 | 178 | // 初期化する 179 | fu.init(); 180 | 181 | // 深さ優先探索を開始する 182 | vector res; 183 | dfs(fu, 0, 0, res); 184 | return res; 185 | } 186 | 187 | // 数詞覆面算用の変数:単語から数値への対応 188 | map dict; 189 | 190 | // 単語集 words から num 個の単語を選んでできる覆面算を作るための再帰関数 191 | void rec_makeup(const vector& words, 192 | int num, 193 | vector& problem, 194 | vector>& res) { 195 | // 終端条件: 必要単語数を揃えた 196 | if (problem.size() == num) { 197 | // 数字が合わない場合はスキップ (数詞覆面算限定) 198 | int sum = 0; 199 | for (int i = 0; i < problem.size() - 1; ++i) { 200 | sum += dict[problem[i]]; 201 | } 202 | if (sum != dict[problem.back()]) return; 203 | 204 | // 覆面算を解く 205 | Fukumenzan fu(problem); 206 | const vector& sols = solve(fu); 207 | 208 | // 一意解ならば答えに格納する 209 | if (sols.size() == 1) res.push_back(problem); 210 | return; 211 | } 212 | 213 | // 新たな単語を加える 214 | for (const string& wd : words) { 215 | problem.push_back(wd); 216 | rec_makeup(words, num, problem, res); 217 | problem.pop_back(); 218 | } 219 | } 220 | 221 | // 単語集 words から num 個の単語を選んでできる覆面算を作る 222 | vector> makeup(const vector& words, int num) { 223 | // 答えを格納する配列 224 | vector> res; 225 | 226 | // 再帰的に解く 227 | vector problem; 228 | rec_makeup(words, num, problem, res); 229 | return res; 230 | } 231 | 232 | int main() { 233 | // 入力 234 | int num_words, num_rows; // 単語数、行数 235 | cin >> num_words >> num_rows; 236 | vector words; // 使用してよい単語 237 | for (int i = 0; i < num_words; ++i) { 238 | string wd; 239 | int val; 240 | cin >> wd >> val; 241 | 242 | // 単語リストと、単語から数字への対応の更新 243 | words.push_back(wd); 244 | dict[wd] = val; 245 | } 246 | 247 | // 一意解となる覆面算を求める 248 | const vector>& res = makeup(words, num_rows); 249 | for (int i = 0; i < res.size(); ++i) { 250 | cout << i << " th problem: " << endl; 251 | for (int row = 0; row < res[i].size(); ++row) { 252 | cout << res[i][row] << " "; 253 | } 254 | cout << endl; 255 | } 256 | } 257 | -------------------------------------------------------------------------------- /solvers/chap2_graph/2_2_fukumen_maker_by_wildcard.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | using namespace std; 7 | 8 | // 覆面算を解くためのクラス 9 | class Fukumenzan { 10 | private: 11 | // 覆面算の文字列を格納した配列 12 | vector problem_; 13 | 14 | // 計算のための変数 15 | vector> board_; // 文字に数字を入れていく過程 16 | set used_; // すでに使用した数字の集合 17 | 18 | // 未確定であることを表す数字 19 | const int NOTIN = -1; 20 | 21 | public: 22 | // コンストラクタ 23 | Fukumenzan(const vector& input) : problem_(input) { } 24 | 25 | // 覆面算を解く過程を初期化する 26 | void init() { 27 | // 文字に数字を入れていく盤面 board 全体を NOTIN で初期化する 28 | board_.resize(problem_.size()); 29 | for (int i = 0; i < problem_.size(); ++i) { 30 | board_[i].assign(problem_[i].size(), NOTIN); 31 | } 32 | used_.clear(); 33 | } 34 | 35 | // 覆面算全体の行数を返す 36 | int get_size() const { return problem_.size(); } 37 | 38 | // 覆面算全体の桁数 (最下行の桁数) を返す 39 | int get_digit() const { return problem_.back().size(); } 40 | 41 | // 覆面算の上から row 行めの桁数を返す 42 | int get_digit(int row) const { return problem_[row].size(); } 43 | 44 | // 数字 val がすでに使われているかどうかを返す 45 | bool is_used(int val) const { return used_.count(val); } 46 | 47 | // 覆面算の上から row 行め、右から digit 桁めに入れた数字を返す 48 | int get_val(int row, int digit) const { 49 | // digit 桁めが row 行めの文字列からはみ出す場合は 0 とする 50 | if (digit >= get_digit(row)) return 0; 51 | return board_[row][get_digit(row) - 1 - digit]; 52 | } 53 | 54 | // 覆面算の上から row 行め、右から digit 桁めの文字を返す 55 | char get_char(int row, int digit) const { 56 | return problem_[row][get_digit(row) - 1 - digit]; 57 | } 58 | 59 | // 覆面算の計算結果を出力する 60 | void print() const { 61 | for (const vector& vec : board_) { 62 | for (int v : vec) cout << v; 63 | cout << endl; 64 | } 65 | } 66 | 67 | // row 行め、右から digit 桁めの文字に数値 val を入れる 68 | void set_val(int row, int digit, int val); 69 | 70 | // row 行め、右から digit 桁めの文字に入れていた数字を削除する 71 | void reset_val(int row, int digit, int val); 72 | 73 | // 覆面文字に入れた数字によって計算が合うかどうかを確認する 74 | bool is_valid(); 75 | }; 76 | 77 | // row 行め、右から digit 桁めの文字に数字 val を入れる 78 | void Fukumenzan::set_val(int row, int digit, int val) { 79 | // 該当の文字を求める 80 | char c = get_char(row, digit); 81 | 82 | // ワイルドカード文字の場合はそのマスのみ変更する 83 | if (c == '?') { 84 | board_[row][get_digit(row) - 1 - digit] = val; 85 | return; 86 | } 87 | 88 | // 文字が c であるすべてのマスに数字 val を入れる 89 | for (int r = 0; r < get_size(); ++r) { 90 | for (int d = 0; d < get_digit(r); ++d) { 91 | if (problem_[r][d] == c) board_[r][d] = val; 92 | } 93 | } 94 | used_.insert(val); // 数字 val を使用済みとする 95 | } 96 | 97 | // row 行め、右から digit 桁めの文字に入れていた数字を削除する 98 | void Fukumenzan::reset_val(int row, int digit, int val) { 99 | // 該当の文字を求める 100 | char c = get_char(row, digit); 101 | 102 | // ワイルドカード文字の場合はそのマスのみ変更する 103 | if (c == '?') { 104 | board_[row][get_digit(row) - 1 - digit] = NOTIN; 105 | return; 106 | } 107 | 108 | // 文字が c であるすべてのマスの数字を削除する 109 | for (int r = 0; r < get_size(); ++r) { 110 | for (int d = 0; d < get_digit(r); ++d) { 111 | if (problem_[r][d] == c) board_[r][d] = -1; 112 | } 113 | } 114 | used_.erase(val); // 数字 val を今後使用可能とする 115 | } 116 | 117 | // 覆面文字に入れた数字によって計算が合うかどうかを確認する 118 | bool Fukumenzan::is_valid() { 119 | // 最上桁に 0 があってはいけない 120 | for (const vector& val : board_) { 121 | if (val[0] == 0) return false; 122 | } 123 | 124 | // 右から 0 桁めから順に計算していく 125 | int kuriagari = 0; // 繰り上がりの値 126 | for (int digit = 0; digit < get_digit(); ++digit) { 127 | // 右から digit 桁めの、最下行以外の行の和を計算する 128 | int sum = 0; 129 | for (int row = 0; row < get_size(); ++row) { 130 | // まだ数字が入っていない部分に来たらその場で終了する 131 | if (get_val(row, digit) == NOTIN) return true; 132 | 133 | // 最下段以外を足す 134 | if (row != get_size() - 1) sum += get_val(row, digit); 135 | } 136 | 137 | // 前の桁からの繰り上がりを足して、新たな繰り上がりを計算する 138 | sum += kuriagari; 139 | kuriagari = sum / 10; 140 | 141 | // 最下行に来るべき数字 (sum % 10) が合っているかを確かめる 142 | if (sum % 10 != get_val(get_size() - 1, digit)) return false; 143 | } 144 | 145 | // 最後に繰り上がりが残ってはいけない 146 | return (kuriagari == 0); 147 | } 148 | 149 | // 深さ優先探索のための再帰関数 150 | // 上から row 行め、右から digit 桁めに数字を入れようとする 151 | void dfs(Fukumenzan& fu, int row, int digit, vector& res) { 152 | // 終端条件: すべての文字に数字が入ったとき 153 | if (row == 0 && digit == fu.get_digit()) { 154 | res.push_back(fu); // 答えに格納する 155 | return; 156 | } 157 | 158 | // 再帰呼び出しのための次のマスを求める 159 | int next_row = row + 1, next_digit = digit; 160 | if (next_row == fu.get_size()) { 161 | next_row = 0, next_digit = digit + 1; 162 | } 163 | 164 | // すでに数字が入っているかどうかで場合分け 165 | if (fu.get_val(row, digit) != -1) { 166 | dfs(fu, next_row, next_digit, res); 167 | } else { 168 | for (int val = 0; val <= 9; ++val) { 169 | // すでに使用済みの数字は使えない (ワイルドカード文字の場合は OK) 170 | if (fu.get_char(row, digit) != '?' && fu.is_used(val)) { 171 | continue; 172 | } 173 | 174 | fu.set_val(row, digit, val); // 文字に数字を入れる 175 | if (fu.is_valid()) { 176 | dfs(fu, next_row, next_digit, res); 177 | } 178 | fu.reset_val(row, digit, val); // 文字から数字を削除する 179 | } 180 | } 181 | } 182 | 183 | // 覆面算を解く関数 184 | vector solve(Fukumenzan& fu) { 185 | // 和の桁数が最大でない場合は解なしなので、空の配列を返す 186 | for (int i = 0; i < fu.get_size() - 1; ++i) { 187 | if (fu.get_digit(i) > fu.get_digit()) { 188 | return vector(); 189 | } 190 | } 191 | 192 | // 初期化する 193 | fu.init(); 194 | 195 | // 深さ優先探索を開始する 196 | vector res; 197 | dfs(fu, 0, 0, res); 198 | return res; 199 | } 200 | 201 | // 一意解となる覆面算をすべて求める 202 | vector> makeup(const vector& input, 203 | const vector& sols) { 204 | // 覆面算を分類する 205 | map, int> groups; 206 | 207 | for (const Fukumenzan& sol : sols) { 208 | // 数字から文字への対応を求める 209 | map dict; 210 | for (int row = 0; row < input.size(); ++row) { 211 | for (int i = 0; i < input[row].size(); ++i) { 212 | // 左から i 番目の数字と文字 213 | int v = sol.get_val(row, input[row].size() - 1 - i); 214 | char c = sol.get_char(row, input[row].size() - 1 - i); 215 | if (c != '?') dict[v] = c; 216 | } 217 | } 218 | 219 | // 改めて '?' を文字に置き換えていく 220 | vector problem(input.size(), ""); 221 | char new_moji = 'a'; 222 | for (int row = 0; row < input.size(); ++row) { 223 | for (int i = 0; i < input[row].size(); ++i) { 224 | int v = sol.get_val(row, input[row].size() - 1 - i); 225 | char c = sol.get_char(row, input[row].size() - 1 - i); 226 | 227 | if (c != '?') { 228 | problem[row] += c; 229 | } else if (dict.count(v)) { 230 | problem[row] += dict[v]; 231 | } else { 232 | // 初登場の数字には新たな文字を割り当てる 233 | dict[v] = new_moji++; 234 | problem[row] += dict[v]; 235 | } 236 | } 237 | } 238 | groups[problem]++; // 完成した覆面算を登録 239 | } 240 | 241 | // グループのメンバーが 1 個のみの覆面算を抽出する 242 | vector> res; 243 | for (const auto& group : groups) { 244 | if (group.second == 1) res.push_back(group.first); 245 | } 246 | return res; 247 | } 248 | 249 | int main() { 250 | // 入力 251 | cout << "Fukumenzan Input: " << endl; 252 | int N; // 行数 253 | cin >> N; 254 | vector input(N); 255 | for (int i = 0; i < N; ++i) cin >> input[i]; 256 | 257 | // 再帰的に解く 258 | Fukumenzan fu(input); 259 | const vector& sols = solve(fu); 260 | 261 | // 一意解となる覆面算を求める 262 | const vector>& res = makeup(input, sols); 263 | for (int i = 0; i < res.size(); ++i) { 264 | cout << i << " th problem: " << endl; 265 | for (int row = 0; row < res[i].size(); ++row) { 266 | cout << res[i][row]; 267 | if (row < res[i].size() - 2) 268 | cout << " + "; 269 | else if (row == res[i].size() - 2) 270 | cout << " = "; 271 | } 272 | cout << endl; 273 | } 274 | } 275 | -------------------------------------------------------------------------------- /solvers/chap2_graph/2_2_fukumen_solver.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | using namespace std; 6 | 7 | // 覆面算を解くためのクラス 8 | class Fukumenzan { 9 | private: 10 | // 覆面算の文字列を格納した配列 11 | vector problem_; 12 | 13 | // 計算のための変数 14 | vector> board_; // 文字に数字を入れていく過程 15 | set used_; // すでに使用した数字の集合 16 | 17 | // 未確定であることを表す数字 18 | const int NOTIN = -1; 19 | 20 | public: 21 | // コンストラクタ 22 | Fukumenzan(const vector& input) : problem_(input) { } 23 | 24 | // 覆面算を解く過程を初期化する 25 | void init() { 26 | // 文字に数字を入れていく盤面 board 全体を NOTIN で初期化する 27 | board_.resize(problem_.size()); 28 | for (int i = 0; i < problem_.size(); ++i) { 29 | board_[i].assign(problem_[i].size(), NOTIN); 30 | } 31 | used_.clear(); 32 | } 33 | 34 | // 覆面算全体の行数を返す 35 | int get_size() const { return problem_.size(); } 36 | 37 | // 覆面算全体の桁数 (最下行の桁数) を返す 38 | int get_digit() const { return problem_.back().size(); } 39 | 40 | // 覆面算の上から row 行めの桁数を返す 41 | int get_digit(int row) const { return problem_[row].size(); } 42 | 43 | // 数字 val がすでに使われているかどうかを返す 44 | bool is_used(int val) const { return used_.count(val); } 45 | 46 | // 覆面算の上から row 行め、右から digit 桁めに入れた数字を返す 47 | int get_val(int row, int digit) const { 48 | // digit 桁めが row 行めの文字列からはみ出す場合は 0 とする 49 | if (digit >= get_digit(row)) return 0; 50 | return board_[row][get_digit(row) - 1 - digit]; 51 | } 52 | 53 | // 覆面算の上から row 行め、右から digit 桁めの文字を返す 54 | char get_char(int row, int digit) const { 55 | return problem_[row][get_digit(row) - 1 - digit]; 56 | } 57 | 58 | // 覆面算の計算結果を出力する 59 | void print() const { 60 | for (const vector& vec : board_) { 61 | for (int v : vec) cout << v; 62 | cout << endl; 63 | } 64 | } 65 | 66 | // row 行め、右から digit 桁めの文字に数値 val を入れる 67 | void set_val(int row, int digit, int val); 68 | 69 | // row 行め、右から digit 桁めの文字に入れていた数字を削除する 70 | void reset_val(int row, int digit, int val); 71 | 72 | // 覆面文字に入れた数字によって計算が合うかどうかを確認する 73 | bool is_valid(); 74 | }; 75 | 76 | // row 行め、右から digit 桁めの文字に数字 val を入れる 77 | void Fukumenzan::set_val(int row, int digit, int val) { 78 | // 該当の文字を求める 79 | char c = get_char(row, digit); 80 | 81 | // 文字が c であるすべてのマスに数字 val を入れる 82 | for (int r = 0; r < get_size(); ++r) { 83 | for (int d = 0; d < get_digit(r); ++d) { 84 | if (problem_[r][d] == c) board_[r][d] = val; 85 | } 86 | } 87 | used_.insert(val); // 数字 val を使用済みとする 88 | } 89 | 90 | // row 行め、右から digit 桁めの文字に入れていた数字を削除する 91 | void Fukumenzan::reset_val(int row, int digit, int val) { 92 | // 該当の文字を求める 93 | char c = get_char(row, digit); 94 | 95 | // 文字が c であるすべてのマスの数字を削除する 96 | for (int r = 0; r < get_size(); ++r) { 97 | for (int d = 0; d < get_digit(r); ++d) { 98 | if (problem_[r][d] == c) board_[r][d] = -1; 99 | } 100 | } 101 | used_.erase(val); // 数字 val を今後使用可能とする 102 | } 103 | 104 | // 覆面文字に入れた数字によって計算が合うかどうかを確認する 105 | bool Fukumenzan::is_valid() { 106 | // 最上桁に 0 があってはいけない 107 | for (const vector& val : board_) { 108 | if (val[0] == 0) return false; 109 | } 110 | 111 | // 右から 0 桁めから順に計算していく 112 | int kuriagari = 0; // 繰り上がりの値 113 | for (int digit = 0; digit < get_digit(); ++digit) { 114 | // 右から digit 桁めの、最下行以外の行の和を計算する 115 | int sum = 0; 116 | for (int row = 0; row < get_size(); ++row) { 117 | // まだ数字が入っていない部分に来たらその場で終了する 118 | if (get_val(row, digit) == NOTIN) return true; 119 | 120 | // 最下段以外を足す 121 | if (row != get_size() - 1) sum += get_val(row, digit); 122 | } 123 | 124 | // 前の桁からの繰り上がりを足して、新たな繰り上がりを計算する 125 | sum += kuriagari; 126 | kuriagari = sum / 10; 127 | 128 | // 最下行に来るべき数字 (sum % 10) が合っているかを確かめる 129 | if (sum % 10 != get_val(get_size() - 1, digit)) return false; 130 | } 131 | 132 | // 最後に繰り上がりが残ってはいけない 133 | return (kuriagari == 0); 134 | } 135 | 136 | // 深さ優先探索のための再帰関数 137 | // 上から row 行め、右から digit 桁めに数字を入れようとする 138 | void dfs(Fukumenzan& fu, int row, int digit, vector& res) { 139 | // 終端条件: すべての文字に数字が入ったとき 140 | if (row == 0 && digit == fu.get_digit()) { 141 | res.push_back(fu); // 答えに格納する 142 | return; 143 | } 144 | 145 | // 再帰呼び出しのための次のマスを求める 146 | int next_row = row + 1, next_digit = digit; 147 | if (next_row == fu.get_size()) { 148 | next_row = 0, next_digit = digit + 1; 149 | } 150 | 151 | // すでに数字が入っているかどうかで場合分け 152 | if (fu.get_val(row, digit) != -1) { 153 | dfs(fu, next_row, next_digit, res); 154 | } else { 155 | for (int val = 0; val <= 9; ++val) { 156 | // すでに使用済みの数字は使えない 157 | if (fu.is_used(val)) continue; 158 | 159 | fu.set_val(row, digit, val); // 文字に数字を入れる 160 | if (fu.is_valid()) { 161 | dfs(fu, next_row, next_digit, res); 162 | } 163 | fu.reset_val(row, digit, val); // 文字から数字を削除する 164 | } 165 | } 166 | } 167 | 168 | // 覆面算を解く関数 169 | vector solve(Fukumenzan& fu) { 170 | // 和の桁数が最大でない場合は解なしなので、空の配列を返す 171 | for (int i = 0; i < fu.get_size() - 1; ++i) { 172 | if (fu.get_digit(i) > fu.get_digit()) { 173 | return vector(); 174 | } 175 | } 176 | 177 | // 初期化する 178 | fu.init(); 179 | 180 | // 深さ優先探索を開始する 181 | vector res; 182 | dfs(fu, 0, 0, res); 183 | return res; 184 | } 185 | 186 | int main() { 187 | // 入力 188 | cout << "Fukumenzan Input: " << endl; 189 | int N; // 行数 190 | cin >> N; 191 | vector input(N); 192 | for (int i = 0; i < N; ++i) cin >> input[i]; 193 | 194 | // 再帰的に解く 195 | Fukumenzan fu(input); 196 | const vector& res = solve(fu); 197 | 198 | // 解を出力 199 | cout << "The num of solutions: " << res.size() << endl; 200 | for (int i = 0; i < res.size(); ++i) { 201 | cout << i << " th solution:" << endl; 202 | res[i].print(); 203 | } 204 | } 205 | -------------------------------------------------------------------------------- /solvers/chap2_graph/2_3_maze_solver.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | using namespace std; 6 | 7 | // 「座標」を表す型を整数値のペアとして定義する 8 | using Cor = pair; 9 | 10 | // 上下左右への移動を定義しておく (0: 下、1: 右、2: 上、3: 左) 11 | const vector DIR = {Cor(1, 0), Cor(0, 1), Cor(-1, 0), Cor(0, -1)}; 12 | 13 | // 未確定であることを表す値 14 | const int NOTIN = -1; 15 | 16 | // 迷路ソルバー 17 | // board: 迷路の盤面。マス (x, y) の情報は board[x][y] で取得する 18 | // start: スタートマスの座標、goal: ゴールマスの座標 19 | void solve(const vector& board, 20 | const Cor& start, const Cor& goal) { 21 | // 盤面の縦サイズと横サイズ 22 | int H = board.size(), W = board[0].size(); 23 | 24 | // nodes[k] := k と書き込まれるマスの集合 25 | vector> nodes; 26 | 27 | // dist[x][y] := マス (x, y) に書き込まれる数値 28 | vector> dist(H, vector(W, NOTIN)); 29 | 30 | // arrow[x][y] := マス (x, y) を始点とする矢印の終点の座標 31 | vector> arrow(H, vector(W, Cor(NOTIN, NOTIN))); 32 | 33 | // スタート地点に関する情報で、探索情報を初期化する 34 | nodes.push_back(vector(1, start)); 35 | dist[start.first][start.second] = 0; 36 | 37 | // 幅優先探索する (最新の nodes が空になるまで実施する) 38 | while (!nodes.back().empty()) { 39 | // 最新の数値が書き込まれたマスの集合 40 | const vector& cur = nodes.back(); 41 | 42 | // 最新の数値が書き込まれたマスから 1 手で行けるマスを求める 43 | vector nex; 44 | for (const Cor& cell : cur) { 45 | // cell の座標値 46 | int x = cell.first, y = cell.second; 47 | 48 | // cell の上下左右のマスを順に調べる 49 | for (int direction = 0; direction < 4; ++direction) { 50 | int nex_x = x + DIR[direction].first; 51 | int nex_y = y + DIR[direction].second; 52 | 53 | // 新たなマスが盤面外になる場合はスキップ 54 | if (nex_x < 0 || nex_x >= H) continue; 55 | if (nex_y < 0 || nex_y >= W) continue; 56 | 57 | // 新たなマスが壁である場合もスキップ 58 | if (board[nex_x][nex_y] == '#') continue; 59 | 60 | // 新たなマスにすでに数値が書き込まれている場合もスキップ 61 | if (dist[nex_x][nex_y] != NOTIN) continue; 62 | 63 | // 新たなマスに数値を書き込む (マス (x, y) より 1 手長い) 64 | dist[nex_x][nex_y] = dist[x][y] + 1; 65 | arrow[nex_x][nex_y] = Cor(x, y); 66 | nex.push_back(Cor(nex_x, nex_y)); 67 | } 68 | } 69 | 70 | // 新たに数値が書き込まれたマスを nodes の末尾に追加する 71 | nodes.push_back(nex); 72 | } 73 | 74 | // スタートマスからゴールマスへ辿り着けない場合は、例外処理する 75 | if (dist[goal.first][goal.second] == NOTIN) { 76 | cout << "No Path" << endl; 77 | return; 78 | } 79 | 80 | // 矢印を辿ることで、経路復元を開始する 81 | vector res = board; // 答えを出力するための盤面 82 | int cur_x = goal.first, cur_y = goal.second; 83 | 84 | // スタート地点からは唯一、矢印が伸びないことに注意 85 | while (arrow[cur_x][cur_y] != Cor(NOTIN, NOTIN)) { 86 | // 最短経路の通路を 'o' で埋める 87 | res[cur_x][cur_y] = 'o'; 88 | 89 | // 矢印をたどる 90 | int nex_x = arrow[cur_x][cur_y].first; 91 | int nex_y = arrow[cur_x][cur_y].second; 92 | cur_x = nex_x, cur_y = nex_y; 93 | } 94 | 95 | // 最短経路を出力する 96 | cout << "----- solution -----" << endl; 97 | for (const string& str : res) cout << str << endl; 98 | cout << "length = " << dist[goal.first][goal.second] << endl; 99 | } 100 | 101 | int main() { 102 | // 入力 103 | cout << "Maze Input: " << endl; 104 | int H, W; // 縦の長さ、横の長さ 105 | cin >> H >> W; 106 | vector board(H); // 盤面 107 | for (int x = 0; x < H; ++x) cin >> board[x]; 108 | 109 | // スタートマスとゴールマスを求める 110 | Cor start, goal; 111 | for (int x = 0; x < H; ++x) { 112 | for (int y = 0; y < W; ++y) { 113 | if (board[x][y] == 'S') 114 | start = Cor(x, y); 115 | else if (board[x][y] == 'G') 116 | goal = Cor(x, y); 117 | } 118 | } 119 | 120 | // 迷路を解く 121 | solve(board, start, goal); 122 | } 123 | -------------------------------------------------------------------------------- /solvers/chap2_graph/2_3_oil_solver.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | using namespace std; 7 | 8 | // 各壺の油の量を頂点とする 9 | using Node = vector; 10 | 11 | // 油分け算ソルバー 12 | void solve(const Node& cap, const Node& start, const Node& goal) { 13 | // dist:探索の始点 start から、各頂点への最短距離 14 | map dist; 15 | dist[start] = 0; 16 | 17 | // arrow:経路復元のための変数 18 | map arrow; 19 | 20 | // オープンリスト todo に頂点 start を挿入する 21 | queue todo; 22 | todo.push(start); 23 | 24 | // オープンリストが空になるまで探索を行う 25 | while (!todo.empty()) { 26 | // オープンリストから頂点を取り出す 27 | Node cur = todo.front(); 28 | todo.pop(); 29 | 30 | // 油の分け方をすべて調べる (壺 from から壺 to へ) 31 | for (int from = 0; from < 3; ++from) { 32 | for (int to = 0; to < 3; ++to) { 33 | if (from == to) continue; 34 | 35 | // 移し替えたあとの状態を求める 36 | Node nex = cur; 37 | if (nex[to] + nex[from] <= cap[to]) { 38 | // 壺 from の油をすべて移しても溢れない場合 39 | nex[to] += nex[from]; 40 | nex[from] = 0; 41 | } else { 42 | // 溢れる場合 43 | nex[from] = nex[from] + nex[to] - cap[to]; 44 | nex[to] = cap[to]; 45 | } 46 | 47 | // すでに発見済みの頂点はスキップ 48 | if (dist.count(nex)) continue; 49 | 50 | // 新たな頂点を探索済みにして、オープンリストに挿入する 51 | dist[nex] = dist[cur] + 1; 52 | arrow[nex] = cur; 53 | todo.push(nex); 54 | } 55 | } 56 | } 57 | 58 | // 不可能の場合 59 | if (!dist.count(goal)) { 60 | cout << "Impossible" << endl; 61 | return; 62 | } 63 | 64 | // 経路復元 65 | vector res; 66 | Node cur = goal; 67 | while (arrow.count(cur)) { 68 | res.push_back(cur); 69 | cur = arrow[cur]; 70 | } 71 | res.push_back(cur); 72 | 73 | // 出力 74 | reverse(res.begin(), res.end()); // 反転する 75 | for (int i = 0; i < res.size(); ++i) { 76 | cout << i << " th: "; 77 | for (int val : res[i]) cout << val << " "; 78 | cout << endl; 79 | } 80 | } 81 | 82 | int main() { 83 | // 各壺の容量の入力 84 | cout << "Cap: "; 85 | Node cap(3); 86 | for (int& val : cap) cin >> val; 87 | 88 | // 各壺に最初に入っている油の容量の入力 89 | cout << "Start: "; 90 | Node start(3); 91 | for (int& val : start) cin >> val; 92 | 93 | // 実現したい、各壺の油の容量の入力 94 | cout << "Goal: "; 95 | Node goal(3); 96 | for (int& val : goal) cin >> val; 97 | 98 | // 油分け算を解く 99 | solve(cap, start, goal); 100 | } 101 | -------------------------------------------------------------------------------- /solvers/chap3_advanced/3_1_15puzzle_solver.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | using namespace std; 7 | 8 | // 「座標」を表す型を整数値のペアとして定義する 9 | using Cor = pair; 10 | 11 | // 二進法表記で表した配置において、座標 pos の情報が含まれる桁を求める 12 | int get_digit(const Cor& pos) { 13 | return (pos.first * 4 + pos.second) * 4; 14 | } 15 | 16 | // 配置 pl の座標 pos の数字を求める 17 | int get_val(uint64_t pl, const Cor& pos) { 18 | // マス (x, y) についての情報のある桁より下の桁を削除する 19 | pl >>= get_digit(pos); 20 | 21 | // 下 4 桁をとる 22 | return pl & 0b1111; 23 | } 24 | 25 | // 「配置」の入力データを受け取る 26 | // emp には空きマスの座標が入力される 27 | uint64_t input(Cor& emp) { 28 | uint64_t pl = 0; 29 | for (int x = 0; x < 4; ++x) { 30 | for (int y = 0; y < 4; ++y) { 31 | uint64_t val; 32 | cin >> val; 33 | if (val == 0) 34 | emp = Cor(x, y); 35 | else 36 | pl += val << get_digit(Cor(x, y)); 37 | } 38 | } 39 | return pl; 40 | } 41 | 42 | // 「配置」を 4 × 4 の形にして出力 43 | void print(uint64_t pl) { 44 | for (int x = 0; x < 4; ++x) { 45 | for (int y = 0; y < 4; ++y) { 46 | cout << get_val(pl, Cor(x, y)) << " "; 47 | } 48 | cout << endl; 49 | } 50 | } 51 | 52 | // 配置 pl のスライド操作後の配置を求める 53 | // val: スライドするタイルの数字 54 | // pos: 配置 pl における、スライドするタイルの座標 55 | // emp: 配置 pl における、空きマスの座標 56 | uint64_t slide(uint64_t pl, int val, 57 | const Cor& pos, const Cor& emp) { 58 | // マス pos の数字 val を削除する 59 | pl -= uint64_t(val) << get_digit(pos); 60 | 61 | // マス emp に数字 val を追加する 62 | pl += uint64_t(val) << get_digit(emp); 63 | return pl; 64 | } 65 | 66 | // 数字 val のタイルの、本来の位置とのマンハッタン距離を計算する 67 | int calc_distance(int val, const Cor& pos) { 68 | // 目標配置におけるパネル val の座標 69 | int x = (val - 1) / 4, y = (val - 1) % 4; 70 | 71 | // マンハッタン距離を求める 72 | return abs(x - pos.first) + abs(y - pos.second); 73 | } 74 | 75 | // 最初の配置 pl の推定値を求める 76 | int estimate(uint64_t pl) { 77 | int res = 0; 78 | 79 | // 配置 pl の座標 (x, y) のタイルを順に見ていく 80 | for (int x = 0; x < 4; ++x) { 81 | for (int y = 0; y < 4; ++y) { 82 | // パネルの値 83 | int val = get_val(pl, Cor(x, y)); 84 | 85 | // 空きマスはスキップ 86 | if (val == 0) continue; 87 | 88 | // マンハッタン距離を加算 89 | res += calc_distance(val, Cor(x, y)); 90 | } 91 | } 92 | return res; 93 | } 94 | 95 | // 推定値 est の配置から、 96 | // 数字 val のタイルを、pos から emp へスライドした配置の推定値を求める 97 | int estimate(int est, int val, const Cor& pos, const Cor& emp) { 98 | return est + calc_distance(val, emp) - calc_distance(val, pos); 99 | } 100 | 101 | // max_depth: 深さ制限値 102 | // depth: 現在の深さ 103 | // pl: 現在の配置 104 | // emp: 現在の配置 pl の空きマスの座標 105 | // est: 現在の配置 pl の推定値 106 | // pre_dir: 現在の配置 pl に至る直前のスライド操作の向き 107 | // res: 配置 pl から目標配置へ至るまでの配置の遷移が格納される (逆順) 108 | void dfs(int max_depth, int depth, 109 | uint64_t pl, const Cor& emp, 110 | int est, int pre_dir, 111 | vector& res) { 112 | // 上下左右への移動を定義しておく (0: 下、1: 右、2: 上、3: 左) 113 | static const int DX[4] = {1, 0, -1, 0}; 114 | static const int DY[4] = {0, 1, 0, -1}; 115 | 116 | // もしすでに解が見つかっていた場合は探索を打ち切る 117 | if (!res.empty()) return; 118 | 119 | // 終端条件(1): 現在の配置 pl が目標配置に一致した場合 120 | if (est == 0) { 121 | res.push_back(pl); 122 | return; 123 | } 124 | 125 | // 終端条件(2): 深さが限界に達した場合は、探索を打ち切る 126 | if (depth >= max_depth) return; 127 | 128 | // 上下左右へのスライド操作を順に試す 129 | for (int dir = 0; dir < 4; ++dir) { 130 | // 直前の配置に戻る操作は実施しない 131 | int reverse_dir = (dir + 2) % 4; 132 | if (reverse_dir == pre_dir) continue; 133 | 134 | // スライドするタイルの座標を計算し、盤面外ならスキップ 135 | int nx = emp.first + DX[dir]; 136 | int ny = emp.second + DY[dir]; 137 | Cor pos(nx, ny); 138 | if (nx < 0 || nx >= 4 || ny < 0 || ny >= 4) continue; 139 | 140 | // 新たな配置を求める 141 | int val = get_val(pl, pos); // スライドするタイルの数字 142 | uint64_t next_pl = slide(pl, val, pos, emp); 143 | 144 | // 新たな配置 next_pl の推定値を求める (差分のみ計算) 145 | int next_est = estimate(est, val, pos, emp); 146 | 147 | // 「枝刈り条件」に引っ掛からなければ、探索を進める 148 | if (depth + next_est <= max_depth) { 149 | // 解が見つかった場合、res は配置の履歴を表す 150 | // 解が見つからなかった場合、res は空ベクトルである 151 | dfs(max_depth, depth + 1, next_pl, pos, next_est, dir, res); 152 | 153 | // 解が見つかった場合の処理 154 | if (!res.empty()) { 155 | // res の末尾に現在の配置 pl を追加する 156 | res.push_back(pl); 157 | return; 158 | } 159 | } 160 | } 161 | } 162 | 163 | // 15 パズルソルバー 164 | // pl: 初期配置、emp: 空きマスの座標 165 | vector solve(const uint64_t& pl, const Cor& emp) { 166 | // 最初の推定値を計算する 167 | int est = estimate(pl); 168 | 169 | // 深さ制限値 max_depth を増やしながら深さ優先探索する 170 | for (int max_depth = 0; max_depth <= 80; ++max_depth) { 171 | vector res; 172 | dfs(max_depth, 0, pl, emp, est, -1, res); 173 | 174 | // 解が見つかったら探索を打ち切る 175 | if (!res.empty()) { 176 | reverse(res.begin(), res.end()); 177 | return res; 178 | } 179 | } 180 | return vector(); 181 | } 182 | 183 | int main() { 184 | // 入力 185 | cout << "15puzzle input: " << endl; 186 | Cor emp; // 空きマスの座標 187 | uint64_t pl = input(emp); 188 | 189 | // 15 パズルを解く 190 | vector res = solve(pl, emp); 191 | 192 | // 出力 193 | for (int i = 0; i < res.size(); ++i) { 194 | cout << "-----" << endl; 195 | cout << i << " th move: " << endl; 196 | print(res[i]); 197 | } 198 | } 199 | -------------------------------------------------------------------------------- /solvers/chap3_advanced/3_2_othello_solver.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | using namespace std; 6 | 7 | // オセロ盤の各マスの座標と、手番を整数値で表す 8 | using Cell = int; 9 | using Color = int; 10 | 11 | // オセロ盤の石の座標の集合も整数値 (0 以上 2^16 未満) で表す 12 | using Stones = int; 13 | 14 | // オセロ盤のサイズと、無限大を表す値 15 | const int SIZE = 4; 16 | const int INF = SIZE * SIZE; // 評価値の理論最大値 17 | 18 | // 色を定義する 19 | const Color BLACK = 1; 20 | const Color WHITE = 0; 21 | 22 | // マス ce の隣接マスを求める (方向 dir) 23 | Cell move(Cell cell, int dir) { 24 | // 周囲 8 マスへの移動ベクトル 25 | static const vector DX = {1, 0, -1, 0, 1, 1, -1, -1}; 26 | static const vector DY = {0, 1, 0, -1, 1, -1, -1, 1}; 27 | 28 | // マス cell の隣接マスの x 座標と y 座標を求める 29 | int x = cell / SIZE + DX[dir]; 30 | int y = cell % SIZE + DY[dir]; 31 | 32 | // 盤外に出る場合は -1 とする 33 | if (x < 0 || x >= SIZE || y < 0 || y >= SIZE) 34 | return -1; 35 | else 36 | return x * SIZE + y; 37 | } 38 | 39 | // 盤面が (black, white) のとき、マス cell の色が col かどうか 40 | bool iscolor(Stones black, Stones white, Color col, Cell cell) { 41 | if (cell == -1) 42 | return false; 43 | if (col == BLACK) 44 | return ((black >> cell) & 1); 45 | else 46 | return ((white >> cell) & 1); 47 | } 48 | 49 | // 盤面が (black, white) のとき、マス cell に色 col の石を置く 50 | // 反転するマスの集合を返す (置けないときは 0 を返す) 51 | Cell put(Stones black, Stones white, Color col, Cell cell) { 52 | // マス cell にすでに石がある場合は置けない 53 | if (((black | white) >> cell) & 1) return 0; 54 | 55 | // 八方向それぞれのひっくり返す石の集合を求めて合わせる 56 | Stones res = 0; 57 | for (int dir = 0; dir < 8; ++dir) { 58 | // 方向 dir についてひっくり返す石の集合を rev とする 59 | Stones rev = 0; 60 | 61 | // 方向 dir を順に見ていく 62 | Cell cell2 = move(cell, dir); 63 | while (iscolor(black, white, 1 - col, cell2)) { 64 | rev |= 1 << cell2; 65 | cell2 = move(cell2, dir); 66 | } 67 | 68 | // きちんと自分の色で挟めている場合、rev を res に加算する 69 | if (iscolor(black, white, col, cell2)) { 70 | res |= rev; 71 | } 72 | } 73 | return res; 74 | } 75 | 76 | // 終局時の得点計算 (手盤が col 側) 77 | int calc(Stones black, Stones white, Color col) { 78 | // 終局時の黒石、白石、空きマスの個数を数える 79 | int num_black = 0, num_white = 0, num_empty = 0; 80 | for (Cell cell = 0; cell < SIZE * SIZE; ++cell) { 81 | if ((black >> cell) & 1) 82 | ++num_black; 83 | else if ((white >> cell) & 1) 84 | ++num_white; 85 | else 86 | ++num_empty; 87 | } 88 | 89 | // 勝利側に空きマスの個数を加算する 90 | if (num_black > num_white) 91 | num_black += num_empty; 92 | else if (num_black < num_white) 93 | num_white += num_empty; 94 | 95 | // 手番に応じて得点を返す 96 | if (col == BLACK) 97 | return num_black - num_white; 98 | else 99 | return num_white - num_black; 100 | } 101 | 102 | // 盤面が (black, white)、手番が col である局面の評価値を求める 103 | int rec(int alpha, int beta, Stones black, Stones white, Color col) { 104 | // 石の置ける場所を求める (mine: 自分、opp: 相手) 105 | vector mine, opp; 106 | for (Cell cell = 0; cell < SIZE * SIZE; ++cell) { 107 | if (put(black, white, col, cell)) 108 | mine.push_back(cell); 109 | if (put(black, white, 1 - col, cell)) 110 | opp.push_back(cell); 111 | } 112 | 113 | // 終局の場合は石差を数えて、手番に応じた得点を返す 114 | if (mine.empty() && opp.empty()) { 115 | return calc(black, white, col); 116 | } 117 | 118 | // 打てる手がないときはパスする 119 | if (mine.empty()) { 120 | return -rec(-beta, -alpha, black, white, 1 - col); 121 | } 122 | 123 | // 無限小を表す値で評価値を初期化する 124 | int res = -INF; 125 | 126 | // 打てる手を順に調べていく 127 | for (Cell cell : mine) { 128 | // 手を打ったあとの盤面を求める 129 | Stones rev = put(black, white, col, cell); 130 | Stones black2 = black ^ rev; 131 | Stones white2 = white ^ rev; 132 | 133 | // マス cell に石を置く 134 | if (col == BLACK) 135 | black2 |= 1 << cell; 136 | else 137 | white2 |= 1 << cell; 138 | 139 | // 遷移局面の評価値を符号反転して受け取る 140 | int score = -rec(-beta, -alpha, black2, white2, 1 - col); 141 | 142 | // その値が最大となる手を選びたい 143 | res = max(res, score); 144 | 145 | // 枝刈り 146 | if (res >= beta) return res; 147 | 148 | // 手番側がこれ以上の得点が得られることは保証できるアルファ値の更新 149 | alpha = max(alpha, res); 150 | } 151 | return res; 152 | } 153 | 154 | int main() { 155 | // 初期配置 (盤面サイズに依存して変わる) 156 | Stones black = (1 << 6) | (1 << 9); // 黒石の配置 157 | Stones white = (1 << 5) | (1 << 10); // 白石の配置 158 | 159 | // α = -16 (理論最小値)、β = 16 (理論最大値)、先手は黒 160 | int score = rec(-INF, INF, black, white, BLACK); 161 | cout << score << endl; 162 | } 163 | -------------------------------------------------------------------------------- /solvers/chap3_advanced/3_3_edit_distance_solver.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | using namespace std; 6 | 7 | // 文字列 S, T の編集距離を求める 8 | int solve(const string& S, const string& T) { 9 | // 2 つの文字列のサイズ 10 | int M = S.size(); 11 | int N = T.size(); 12 | 13 | // 動的計画法のための配列 14 | // 配列全体を無限大を表す値で初期化する (ここでは N + M) 15 | vector> dp(M + 1, vector(N + 1, N + M)); 16 | 17 | // 動的計画法の初期条件 18 | dp[0][0] = 0; 19 | 20 | // 各頂点への最短距離を順に求めていく 21 | for (int x = 0; x <= M; ++x) { 22 | for (int y = 0; y <= N; ++y) { 23 | // 上の頂点から来る経路を考慮する 24 | if (x > 0) { 25 | dp[x][y] = min(dp[x][y], dp[x - 1][y] + 1); 26 | } 27 | 28 | // 左の頂点から来る経路を考慮する 29 | if (y > 0) { 30 | dp[x][y] = min(dp[x][y], dp[x][y - 1] + 1); 31 | } 32 | 33 | // 左上の頂点から来る経路を考慮する 34 | if (x > 0 && y > 0) { 35 | // 文字が一致する場合は辺の長さは 0 になる 36 | int length = 1; 37 | if (S[x - 1] == T[y - 1]) length = 0; 38 | 39 | // 更新 40 | dp[x][y] = min(dp[x][y], dp[x - 1][y - 1] + length); 41 | } 42 | } 43 | } 44 | 45 | // 右下頂点の値を返す 46 | return dp[M][N]; 47 | } 48 | 49 | int main() { 50 | // 入力 51 | string S, T; 52 | cout << "First String: "; 53 | cin >> S; 54 | cout << "Second String: "; 55 | cin >> T; 56 | 57 | // 文字列 S、T の編集距離を求める 58 | cout << solve(S, T) << endl; 59 | } 60 | -------------------------------------------------------------------------------- /solvers/chap3_advanced/3_4_domino_solver.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | using namespace std; 6 | 7 | // 二部マッチングを解くためのクラス 8 | class BipartiteMatching { 9 | private: 10 | // 左側頂点数と右側頂点数 11 | int size_left, size_right; 12 | 13 | // list[l]: 左側頂点 l に隣接する右側頂点の集合 14 | vector> list; 15 | 16 | // 計算結果 17 | // l2r[l]: 左側頂点 l とペアにする右側頂点 18 | // r2l[r]: 右側頂点 r とペアにする左側頂点 19 | // マッチングしていない部分は -1 とする 20 | vector l2r, r2l; 21 | 22 | public: 23 | // コンストラクタ 24 | BipartiteMatching(int sl, int sr) : 25 | size_left(sl), size_right(sr), list(sl) {} 26 | 27 | // 二部グラフの辺を追加していく 28 | // 左側頂点 l と右側頂点 r を結ぶ 29 | void add_edge(int l, int r) { list[l].push_back(r); } 30 | 31 | // 左側頂点 l を始点とした増加路を探索する 32 | // seen[l]: 左側頂点 l からの探索が実施済みかどうか 33 | // 返り値: 存在するとき true、しないとき false 34 | bool dfs(int l, vector& seen) { 35 | // すでに探索済みの場合は false を返す 36 | if (seen[l]) return false; 37 | 38 | // 探索済みの状態にする 39 | seen[l] = true; 40 | 41 | // 右側頂点を順に探索する 42 | for (int r : list[l]) { 43 | // 右側頂点 r がフリーである場合は true を返す 44 | // または、すでにマッチングしている左側頂点へ向かって 45 | // 増加路が見つかる場合も true を返す 46 | if (r2l[r] == -1 || dfs(r2l[r], seen)) { 47 | l2r[l] = r; 48 | r2l[r] = l; 49 | return true; 50 | } 51 | } 52 | 53 | // 増加路が見つからない場合は false を返す 54 | return false; 55 | } 56 | 57 | // 二部マッチング問題を解く 58 | // 返り値: マッチング辺 (左側頂点, 右側頂点) の集合 59 | vector> solve() { 60 | // マッチング辺 61 | vector> res; 62 | 63 | // 初期化 64 | l2r.assign(size_left, -1); 65 | r2l.assign(size_right, -1); 66 | 67 | // 増加路が見つからなくなるまで反復する 68 | while (true) { 69 | // 更新が発生したかどうかを表すフラグ変数 70 | bool update = false; 71 | 72 | // 同じ左側頂点を重複して探索することを防ぐ 73 | vector seen(size_left, false); 74 | 75 | // 左側頂点 l を順に探索する 76 | for (int l = 0; l < size_left; ++l) { 77 | // すでにマッチング済みの場合はスキップする 78 | if (l2r[l] != -1) continue; 79 | 80 | // 増加路が見つかった場合、フラグを立てて繰り返す 81 | if (dfs(l, seen)) { 82 | update = true; 83 | break; 84 | } 85 | } 86 | 87 | // 増加路が見つからない場合は終了する 88 | if (!update) break; 89 | } 90 | 91 | // マッチング辺を復元する 92 | for (int l = 0; l < size_left; ++l) { 93 | if (l2r[l] != -1) res.emplace_back(l, l2r[l]); 94 | } 95 | return res; 96 | } 97 | }; 98 | 99 | // ドミノタイリングソルバー (盤面にタイリングを埋め込む) 100 | // 返り値は、敷き詰めたドミノの個数 101 | int solve(vector& board) { 102 | // 上下左右の 4 マスへの移動を表すベクトル 103 | static const vector dx = {1, 0, -1, 0}; 104 | static const vector dy = {0, 1, 0, -1}; 105 | 106 | // 盤面のサイズ 107 | int H = board.size(), W = board[0].size(); 108 | 109 | // 二部グラフを構築する 110 | BipartiteMatching bm(H * W, H * W); 111 | for (int i = 0; i < H; ++i) { 112 | for (int j = 0; j < W; ++j) { 113 | // 'x' の場合はスキップする 114 | if (board[i][j] == 'x') continue; 115 | 116 | // 黒色マスでない場合はスキップする 117 | if ((i + j) % 2) continue; 118 | 119 | // 上下左右を調べる 120 | for (int dir = 0; dir < 4; ++dir) { 121 | int ni = i + dx[dir]; 122 | int nj = j + dy[dir]; 123 | 124 | // 盤面外の場合はスキップする 125 | if (ni < 0 || ni >= H || nj < 0 || nj >= W) continue; 126 | 127 | // 'x' の場合はスキップする 128 | if (board[ni][nj] == 'x') continue; 129 | 130 | // 二部グラフの辺を追加する 131 | bm.add_edge(i * W + j, ni * W + nj); 132 | } 133 | } 134 | } 135 | 136 | // 二部マッチングの解を得る 137 | const vector>& res = bm.solve(); 138 | 139 | // 盤面にタイリングを埋め込む 140 | for (const pair& lr : res) { 141 | int l = lr.first, r = lr.second; 142 | int li = l / W, lj = l % W; 143 | int ri = r / W, rj = r % W; 144 | 145 | if (li == ri) { 146 | // 横に並べるとき 147 | board[li][lj] = '-'; 148 | board[ri][rj] = '-'; 149 | } else { 150 | // 縦に並べるとき 151 | board[li][lj] = '|'; 152 | board[ri][rj] = '|'; 153 | } 154 | } 155 | return res.size(); 156 | } 157 | 158 | int main() { 159 | // 入力 160 | cout << "Domino Tiling Input:" << endl; 161 | int H, W; 162 | cin >> H >> W; 163 | vector board(H); 164 | for (string& row : board) cin >> row; 165 | 166 | // ドミノタイリングを解く 167 | int max_num = solve(board); 168 | 169 | // 解を出力 170 | cout << "num of domino: " << max_num << endl; 171 | for (const string& row : board) cout << row << endl; 172 | } 173 | --------------------------------------------------------------------------------