Skip to content

Commit

Permalink
update
Browse files Browse the repository at this point in the history
  • Loading branch information
Reputeless committed Dec 19, 2021
1 parent 2c83d27 commit 4b6fecc
Show file tree
Hide file tree
Showing 11 changed files with 260 additions and 2 deletions.
104 changes: 103 additions & 1 deletion 004.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,105 @@
# 004 - Cross Sum (★2)

## 解答 1
```cpp
#include <iostream>
#include <vector>

int main()
{
// H 行, W 列
int H, W;
std::cin >> H >> W;

// マス目 (大きさ W の vector が H 個)
std::vector<std::vector<int>> A(H, std::vector<int>(W));

// 各行の合計 (H 個)
std::vector<int> rowSums(H);

// 各列の合計 (W 個)
std::vector<int> 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 <iostream>
#include <vector>

int main()
{
int H = 4, W = 3;
std::vector<std::vector<int>> 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 <iostream>
#include <vector>

int main()
{
int H = 4, W = 3;
std::vector<int> 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 <iostream>
#include <vector>
Expand Down Expand Up @@ -54,7 +154,9 @@ int main()
}
```

## デバッグ出力付き
## デバッグ出力付き解答コード
プログラムの途中で `std::vector` にどのような値が格納されているか確認したい場合は、次のようにデバッグ出力を加えると良い。

```cpp
#include <iostream>
#include <vector>
Expand Down
2 changes: 2 additions & 0 deletions 010.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# 010 - Score Sum Queries (★2)

## 解答

```cpp
#include <iostream>
#include <vector>
Expand Down
2 changes: 2 additions & 0 deletions 022.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# 022 - Cubic Cake (★2)

## 解答

最大公約数を求めるために、C++ 標準ライブラリの [`std::gcd(x, y)`](https://cpprefjp.github.io/reference/numeric/gcd.html) を使います。

```cpp
Expand Down
2 changes: 2 additions & 0 deletions 024.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# 024 - Select +/- One (★2)

## 解答

```cpp
#include <iostream>
#include <vector>
Expand Down
2 changes: 2 additions & 0 deletions 027.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# 027 - Sign Up Requests (★2)

## 解答

`std::unordered_set::insert()` は戻り値として `std:pair<iterator, bool>` を返します。この `.second` 部 (`bool`) は、コンテナへのキーの追加に成功した(コンテナに同じキーが存在しなかった)場合に `true`, 追加に失敗した(コンテナに同じキーが既に存在した)場合に `false` を返します。

```cpp
Expand Down
2 changes: 2 additions & 0 deletions 033.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# 033 - Not Too Bright (★2)

## 解答

```cpp
#include <iostream>

Expand Down
137 changes: 137 additions & 0 deletions 055.md
Original file line number Diff line number Diff line change
@@ -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 <iostream>
#include <vector>

int main()
{
// N 個の整数、P で割った余りが Q
int N, P, Q;
std::cin >> N >> P >> Q;

// 整数の入力
std::vector<int> 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 <iostream>
#include <vector>
#include <atcoder/modint.hpp> // atcoder::modint

int main()
{
// N 個の整数、P で割った余りが Q
int N, P, Q;
std::cin >> N >> P >> Q;

atcoder::modint::set_mod(P); // mod を設定

// 整数の入力
std::vector<atcoder::modint> 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';
}
```
2 changes: 2 additions & 0 deletions 061.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# 061 - Deck (★2)

## 解答

```cpp

```
2 changes: 2 additions & 0 deletions 067.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# 067 - Base 8 to 9 (★2)

## 解答

```cpp

```
2 changes: 2 additions & 0 deletions 078.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# 078 - Easy Graph Problem (★2)

## 解答

```cpp

```
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
# C++17 で解く「競プロ典型 90 問」

**「競プロ典型 90 問」** を、ほぼすべての行にコメントのある、わかりやすい C++17 コードで解く、[@Reputeless](https://twitter.com/Reputeless) によるプロジェクトです。
**「競プロ典型 90 問」** を、わかりやすい C++17 コードで解く、[@Reputeless](https://twitter.com/Reputeless) によるプロジェクトです。

競技プログラミング固有のハックやスタイル(`<bits/stdc++.h>`, 大きな配列、マクロ、`using namespace std` 等)の使用を避けているため、一般的な C++ ソフトウェア開発でも使える、また C++ 標準ライブラリの機能 (`std::` から始まる) を意識したコーディングの練習ができます。

不具合や改善案は、このリポジトリの Issue よりご報告ください。

### ★2

|問題|タイトル (解答コードへのリンク)|難易度|公式解説|キーワード (公式解説から引用)|
Expand Down Expand Up @@ -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)

0 comments on commit 4b6fecc

Please sign in to comment.