From 4b6feccf8e8cdf3b34f3442af362f8409e898587 Mon Sep 17 00:00:00 2001 From: Ryo Suzuki Date: Sun, 19 Dec 2021 14:11:51 +0900 Subject: [PATCH] update --- 004.md | 104 ++++++++++++++++++++++++++++++++++++++++- 010.md | 2 + 022.md | 2 + 024.md | 2 + 027.md | 2 + 033.md | 2 + 055.md | 137 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 061.md | 2 + 067.md | 2 + 078.md | 2 + README.md | 5 +- 11 files changed, 260 insertions(+), 2 deletions(-) diff --git a/004.md b/004.md index 4cc136b..7d13f22 100644 --- a/004.md +++ b/004.md @@ -1,5 +1,105 @@ # 004 - Cross Sum (★2) +## 解答 1 +```cpp +#include +#include + +int main() +{ + // H 行, W 列 + int H, W; + std::cin >> H >> W; + + // マス目 (大きさ W の vector が H 個) + std::vector> A(H, std::vector(W)); + + // 各行の合計 (H 個) + std::vector rowSums(H); + + // 各列の合計 (W 個) + std::vector colSums(W); + + // マス目の入力 + for (int y = 0; y < H; ++y) // 上から y 行目 + { + for (int x = 0; x < W; ++x) // 左から x 列目 + { + int a; + std::cin >> a; + + // 現在のマスを記録 + A[y][x] = a; + + // 現在の行の合計値を増やす + rowSums[y] += a; + + // 現在の列の合計値を増やす + colSums[x] += a; + } + } + + // 解答の出力 + for (int y = 0; y < H; ++y) // 上から y 行目 + { + for (int x = 0; x < W; ++x) // 左から x 列目 + { + // 現在の行の合計値 + 現在の列の合計値 - 現在のマス + const int a = (rowSums[y] + colSums[x] - A[y][x]); + + std::cout << a << ' '; + } + + std::cout << '\n'; + } +} +``` + +## 解答 2 (二次元配列を作らない) +二次元配列は一次元配列で表現することもできる。 + +例: +```cpp +#include +#include + +int main() +{ + int H = 4, W = 3; + std::vector> v = + { + {11, 12, 13}, + {21, 22, 23}, + {31, 32, 33}, + {41, 42, 43}, + }; + + int y = 3, x = 2; + std::cout << v[y][x] << '\n'; //43 +} +``` +を +```cpp +#include +#include + +int main() +{ + int H = 4, W = 3; + std::vector v = + { + 11, 12, 13, + 21, 22, 23, + 31, 32, 33, + 41, 42, 43, + }; + + int y = 3, x = 2; + std::cout << v[3 * W + 2] << '\n'; //43 +} +``` +で表す。 + ```cpp #include #include @@ -54,7 +154,9 @@ int main() } ``` -## デバッグ出力付き +## デバッグ出力付き解答コード +プログラムの途中で `std::vector` にどのような値が格納されているか確認したい場合は、次のようにデバッグ出力を加えると良い。 + ```cpp #include #include diff --git a/010.md b/010.md index b272535..3bf63ad 100644 --- a/010.md +++ b/010.md @@ -1,5 +1,7 @@ # 010 - Score Sum Queries (★2) +## 解答 + ```cpp #include #include diff --git a/022.md b/022.md index 8f17c2d..09e2275 100644 --- a/022.md +++ b/022.md @@ -1,5 +1,7 @@ # 022 - Cubic Cake (★2) +## 解答 + 最大公約数を求めるために、C++ 標準ライブラリの [`std::gcd(x, y)`](https://cpprefjp.github.io/reference/numeric/gcd.html) を使います。 ```cpp diff --git a/024.md b/024.md index 11794cc..08b23a2 100644 --- a/024.md +++ b/024.md @@ -1,5 +1,7 @@ # 024 - Select +/- One (★2) +## 解答 + ```cpp #include #include diff --git a/027.md b/027.md index dfd3e25..5bad589 100644 --- a/027.md +++ b/027.md @@ -1,5 +1,7 @@ # 027 - Sign Up Requests (★2) +## 解答 + `std::unordered_set::insert()` は戻り値として `std:pair` を返します。この `.second` 部 (`bool`) は、コンテナへのキーの追加に成功した(コンテナに同じキーが存在しなかった)場合に `true`, 追加に失敗した(コンテナに同じキーが既に存在した)場合に `false` を返します。 ```cpp diff --git a/033.md b/033.md index 1842c15..402ffaa 100644 --- a/033.md +++ b/033.md @@ -1,5 +1,7 @@ # 033 - Not Too Bright (★2) +## 解答 + ```cpp #include diff --git a/055.md b/055.md index b7f91e3..717a606 100644 --- a/055.md +++ b/055.md @@ -1,5 +1,142 @@ # 055 - Select 5 (★2) +## 解答 1 + +``` +(a * b) % p = a % p * b % p + +(a * b * c) % p = a % p * b % p * c % p + +... +``` + +を利用することで、5 つの数の積が整数オーバーフローすることを回避する。(なお、`%` の優先度は `*` と等しい) + +例: +``` +(11 * 12 * 13) % 10 += 11 % 10 * 12 % 10 * 13 % 10 += 1 * 12 % 10 * 13 % 10 += 12 % 10 * 13 % 10 += 2 * 13 % 10 += 26 % 10 += 6 +``` + +```cpp +#include +#include + +int main() +{ + // N 個の整数、P で割った余りが Q + int N, P, Q; + std::cin >> N >> P >> Q; + + // 整数の入力 + std::vector A(N); + for (auto& a : A) + { + std::cin >> a; + } + + // 成立した組み合わせの個数 + int count = 0; + + for (int i = 0; i < (N - 4); ++i) + { + // 整数オーバーフローしないよう long long 型に + const long long a = A[i]; + + for (int j = (i + 1); j < (N - 3); ++j) + { + const long long b = a * A[j] % P; + + for (int k = (j + 1); k < (N - 2); ++k) + { + const long long c = b * A[k] % P; + + for (int l = (k + 1); l < (N - 1); ++l) + { + const long long d = c * A[l] % P; + + for (int m = (l + 1); m < N; ++m) + { + const long long e = d * A[m] % P; + + if (e == Q) + { + ++count; + } + } + } + } + } + } + + std::cout << count << '\n'; +} +``` + +## 解答 2 (定数倍高速化) +この問題における mod 演算は、[Barrett reduction](https://en.wikipedia.org/wiki/Barrett_reduction) というテクニックを使うことで、計算に時間がかかる `%` 演算子の使用を回避して定数倍高速化できる。AC Library の `atcoder::modint` ライブラリには Barrett reduction が実装されているため、それを利用するとコードも短くなり一石二鳥である。 + +なお、`atcoder::modint` は、「C++ 標準の整数型 → `atcoder::modint`」の変換に通常の `%` 演算を使うため、プログラム全体を通してこの変換回数を減らすよう設計しないと速度向上効果が出ない(かえって遅くなることもある)。次のように、入力された値を最初にすべて `atcoder::modint` に変換して格納し、以降変換が発生しないようにする。 ```cpp +#include +#include +#include // atcoder::modint + +int main() +{ + // N 個の整数、P で割った余りが Q + int N, P, Q; + std::cin >> N >> P >> Q; + + atcoder::modint::set_mod(P); // mod を設定 + + // 整数の入力 + std::vector A(N); + for (auto& a : A) + { + int t; + std::cin >> t; + a )= atcoder::modint{ t }; + } + + // 成立した組み合わせの個数 + int count = 0; + + for (int i = 0; i < (N - 4); ++i) + { + const atcoder::modint a = A[i]; + + for (int j = (i + 1); j < (N - 3); ++j) + { + const atcoder::modint b = a * A[j]; + + for (int k = (j + 1); k < (N - 2); ++k) + { + const atcoder::modint c = b * A[k]; + + for (int l = (k + 1); l < (N - 1); ++l) + { + const atcoder::modint d = c * A[l]; + + for (int m = (l + 1); m < N; ++m) + { + const atcoder::modint e = d * A[m]; + + if (e.val() == Q) + { + ++count; + } + } + } + } + } + } + std::cout << count << '\n'; +} ``` diff --git a/061.md b/061.md index 2580b5d..6db062b 100644 --- a/061.md +++ b/061.md @@ -1,5 +1,7 @@ # 061 - Deck (★2) +## 解答 + ```cpp ``` diff --git a/067.md b/067.md index 2283d6d..9ba37b1 100644 --- a/067.md +++ b/067.md @@ -1,5 +1,7 @@ # 067 - Base 8 to 9 (★2) +## 解答 + ```cpp ``` diff --git a/078.md b/078.md index 64bb546..3ac6dcd 100644 --- a/078.md +++ b/078.md @@ -1,5 +1,7 @@ # 078 - Easy Graph Problem (★2) +## 解答 + ```cpp ``` diff --git a/README.md b/README.md index f7e4ed6..ff17fb6 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,11 @@ # C++17 で解く「競プロ典型 90 問」 -**「競プロ典型 90 問」** を、ほぼすべての行にコメントのある、わかりやすい C++17 コードで解く、[@Reputeless](https://twitter.com/Reputeless) によるプロジェクトです。 +**「競プロ典型 90 問」** を、わかりやすい C++17 コードで解く、[@Reputeless](https://twitter.com/Reputeless) によるプロジェクトです。 競技プログラミング固有のハックやスタイル(``, 大きな配列、マクロ、`using namespace std` 等)の使用を避けているため、一般的な C++ ソフトウェア開発でも使える、また C++ 標準ライブラリの機能 (`std::` から始まる) を意識したコーディングの練習ができます。 +不具合や改善案は、このリポジトリの Issue よりご報告ください。 + ### ★2 |問題|タイトル (解答コードへのリンク)|難易度|公式解説|キーワード (公式解説から引用)| @@ -48,5 +50,6 @@ ### 参考リンク - [競プロ典型 90 問 - AtCoder コンテストページ](https://atcoder.jp/contests/typical90) - [競プロ典型 90 問 - GitHub 公式リポジトリ](https://github.com/E869120/kyopro_educational_90) +- [競プロ典型 90 問 - テストケース](https://www.dropbox.com/sh/nx3tnilzqz7df8a/AAC-L790bxKBVkmB6pdMUgk4a/typical90?dl=0&subfolder_nav_tracking=1) - [AtCoder での実力アップを目指そう! ~競プロ典型 90 問~ - Qiita](https://qiita.com/e869120/items/1b2a5f0f07fd927e44e9) - [「競プロ典型90問」非公式難易度表・ソースコード共有 - Google スプレッドシート](https://docs.google.com/spreadsheets/d/1GG4Higis4n4GJBViVltjcbuNfyr31PzUY_ZY1zh2GuI/edit#gid=0)