t6o_o6tのAtCoder記録
t6o_o6t
分散していて逆に確認しにくいのではないか、と思い書く
振り返りが重要だが、システムで解決する方法を考えていないのでこうする
何も読まずにできる問題をやる必要はない。
できない問題はできるようになってもやる必要がある。
2023/08/02
★ AはBの約数
A - B +/- A
BはAで割り切れる = BをAで割った余りは0
★ 変数の初期化は必要か?
code:init_var.cpp
int f() {
int ans, i;
for (i = 0; i < 5; i++) {
ans += i;
}
return ans;
}
みたいな処理を書いたら、ansが不思議な数値になった
コメント
変数は必ず初期化する。どのような状態であれ、一番安全を志向するのは当然のことである。
★ long longを使う
$ 10^8までの値は intでも大丈夫だと思う
基本的に安全のため、大きくなる変数をlong longで確保すべきである
足し算、比較は問題なく行えると思う
2023/08/03
「もし〇〇でなかったら」の脳のバグ
書きたかった処理
FizzBuzzにおいて、「ある数値に対応するのが数値でなかったら(=FizzやBuzzでないなら)」次のループに移る
実際に書いてしまったもの
code:invalid.cpp
for (i = 1; i <= N; i++) {
if (i % 3 || i % 5) continue;
total += i;
}
コメント
これでは、3や5の倍数でないときを弾いてしまう
3や5の倍数でないときに処理したいのに
条件式はなるべくメンタルモデルに沿う形で書きたい
もしメンタルモデルを出力する際に一定の混乱があるなら、工夫する
条件式を変数にして分割する
ド・モルガンの法則で論理記号を変換する
複数のif文に分割して段階的に
2023/08/04
★ C++のソートはデフォルトで小さい順
greater<int>で大きい順ソートする
Card Game For Two
2023/08/14
このレベルが難しいラインっぽい
https://atcoder.jp/contests/abc113/tasks/abc113_c
✅ 解説AC
解説ACが、最も個人の経験をよく表すのではないか。
解説なしACは、元々解けていた可能性がある
解説あってもACできなけれれば、レベルが高すぎた
解説ACは、本人が一定の知識を学び取った証である
難しかったポイント
★ 入力値を配列のインデックスに使うときは注意
AtCoderでは、しばしば入力値が1始まりで、そのまま配列のインデックスに使ってはならない
★ 実行時エラーが表示されない
データ構造
★ intを含むpairのvectorを配列に変形する
スマートにいくケースがある
★ ゼロ埋め出力
%06dで6桁でゼロ埋めできた
★ char* を stringにする
string()
2023/08/16
難しい問題来た
https://atcoder.jp/contests/abc104/tasks/abc104_b
✅ 解説なしAC
自分で問題点を探せたから解説なしでAC
ただ何回かテストした
ミスした点
部分文字列の書き方は?
substrを使うが、何文字文切り取りたいのか要注意
今回は、s.length() - 3 文字分であることを、例を用いて確かめた
ABCDEFG → CDEF(7 - 3 = 4)
「1個だけ存在する」条件の実装
Cが部分文字列に1個だけ存在する
楽しく書けた
https://atcoder.jp/contests/abc215/tasks/abc215_a
しかしバグはあった
★ 同一文字列の判定
1. 文字列長が同じであることを示す
2. 先頭から順番に、文字が同じであることをたしかめる
命名は大文字より小文字
命名は短いほうが読みやすい
難しいロジックを書く場合には、命名が長いほうが良いのかもしれないが
競技プログラミングは、(現時点では)長くて100行という感じ
読みにくければ関数に切り出すだけであり、変数名を短く保ったほうが読み書きしやすい
分からなかった
https://atcoder.jp/contests/abc214/tasks/abc214_b
非負整数 a, b, c をどこまで探索して良いのかが
だが、これはsを条件に指定するだけでうまくいく
★ 初めは思いついたものを書く
〇〇しないと間に合わない、と決めつけない
熟練者は勘と計算量で分かるが、今はまだ分かっていないはず
後から最適化していく方が良い
t を条件に使ってはいけない理由
a, b, c のいずれかが 0 のとき、必ず積が0になってしまい、本来探索されるべきだった組み合わせも打ち切ってしまうから。
ちょっとムズカシイ
https://atcoder.jp/contests/abc177/tasks/abc177_b
考えさせられた
Tに対してすべての文字を検証しなければならないというのがポイント
✅解説AC
https://atcoder.jp/contests/abc157/tasks/abc157_b
★ 盤面における左下方向の斜め探索の注意点
[0][2]、[1][1]、[2][0]などとなっていて気づいた
[2 - x][2 - x]ではなく[x][2 - x]だ
rowは単純に増やせばよいが、columnは右から左へ減らしていく必要がある
盲点
一番消耗した
https://atcoder.jp/contests/abc197/tasks/abc197_b
1. ★ AtCoderからの入力値を添字として取り扱ってはいけない
AtCoderは必ず1始まりの値を入力してくるので、添字として取り扱ってはならない
二次元配列で形状を管理する場合が盲点だった。
この場合も、AtCoderの入力値を添字に使ってはならない。
怖いので 1 引くのをくせにしたい。
2. ★ AtCoderにおけるYは必ずしも高さではない
H、W、X、Yという4つの入力は、Hが高さ、Wが幅で、Xは横の座標、Yは縦の座標だと思っていた ーーー
違った
Xが縦の座標(1次元目)で、Yは横の座標(2次元目)という関係だった
今まで書いてきたものが全くおかしかったことがわかった
解説なしAC... だけど、学び(?)の多かった時間だった
本当の学びとは、学び(?)なのかもしれない
自分の中で学びと呼ぶには少しモヤッとするもの
WSL上でVSCodeのC++デバッグを使用する
https://zenn.dev/boiledorange73/articles/0056-wsl-vsc-gcc をそのままやったら上手く行った。
この記事の内容を忠実に実行するというのが重要
自分は、VSCodeが補完してきた launch.json の値を使って失敗していた
gdb(起動)というタスク
それまでVSCodeのC++デバッグでブレークポイントが認識されない状態だった
記事の内容を元にやり直したら上手く行ったという経緯
解説なしAC
https://atcoder.jp/contests/abc107/tasks/abc107_b
もう、B問題とか関係ない
良い問題は、番号に関係なく良い問題である
盤面系の問題は全体的に体力を使う
ただ、今後も様々な問題を解いたら、安定させられそうである
初級編をとりあえず終わる
解説AC.icon
https://atcoder.jp/contests/abc085/tasks/abc085_c
問題認識❌
❌N枚以下でY円になる組み合わせ
◯ N枚でちょうどY円になる組み合わせ
文章を正しく読めるのが前提になっている
今の強化ポイントはそこ
3重ループだと間に合わないけど
N枚ちょうどなら、3種のうち1種の枚数は一意に定まるよね、というのを
覚えていた...
悪いことではない、学んだということ
解説なしAC / 解説を読んでなるほど
https://atcoder.jp/contests/abc097/tasks/abc097_b
解説
https://blog.hamayanhamayan.com/entry/2018/05/13/083354
初めに書いたコードは、bとp、2つの変数でループし、powで累乗を計算していた
★ 累乗値を探索する場合powは必要ない
現在の値に、底を掛けて、ついでに他の処理も行うようにすると良い
解説なしAC
いまいちWAが腑に落ちない
code:ac.cpp
if (z < 0 || k < z)
continue;
breakしてはいけない理由は何か?
k < zを考慮する必要があるのはなぜか?
z = 15となるパターンがあった
理由
$ z = s-x-y から、$ z\le3kが導かれた
解説なしAC
@e869120: 【56 日目】
昨日の解説と今日の典型問題です。標準的な難易度となっています。(入力形式・入出力例は GitHub を参照のこと)
なお、解説の都合上、本日に限り Python の想定解も GitHub に掲載しております。 #競プロ典型90問
https://pbs.twimg.com/media/E21MVj5VEAEVr3l.jpghttps://pbs.twimg.com/media/E21MXDmUcAEnTjK.jpg
★ 積を取ることによるオーバーフローは余りを取って対処する。
計算量が、$ O(N^5)と大きくても、定数倍を見積もることで間に合う解答であると判断できるのは盲点だった。
2023/08/18
解説AC✅
https://atcoder.jp/contests/abc081/tasks/arc086_a
AtCoderにおけるURLが特殊な問題
普段使っているAtCoderでテストケースの実行を自動化が動かなくてぱにった
そのかわり、デバッグ機能を上手く設定できたので楽になった
unsignedとsignedの引き算の結果をforの条件に使ってしまい無限ループ
解説なしAC
https://atcoder.jp/contests/typical90/tasks/typical90_aa
ここまで同じような問題を解いて、典型が「視えた」
この典型では、setが鬼強
解説なしAC
https://atcoder.jp/contests/abc073/tasks/abc073_c
テストケースに不備があるのではないか
$ A_iの範囲は本来 int だとオーバーフローする
今回は、オーバーフローした際の数値が重複するテストケースがなかったと思われる
int で入力値を取ったり、mapのキーにしても通ってしまった
解説なしAC
https://atcoder.jp/contests/abc155/tasks/abc155_c
Poll
教育的
C++のmapはキーで自動的に整列される
キーが文字列なら辞書順になっているということ
解説ありAC
https://atcoder.jp/contests/abc058/tasks/arc071_a
とても時間がかかった
Difficulty 680
AtCoder茶に相当?
当たった問題
★ mapのイテレーション中にキーをeraseしてエラー
削除したいキーのリストを作って、イテレーションが終わったあとにeraseした
方針は途中で無理なく立った
実装力が少し付いた気がする。良かった
本によって理解の優先度付けができるのかもしれない
大量の問題をいきなりすべて覚える必要はない
できるなら、やったほうがよいが
問題カテゴリへの入り口が明示されている
入り口の内容を熟読して、段階的に慣れていくことが可能
ただ無作為に並べられたAtCoderの問題ではこれはできない
Qiitaの記事も「段階的」に出来なかった
ブラウザだとすべてが視えてしまう
ゲームのように、「これが出来たら次のステージ」が出来たら良かったのか
でもある程度自由度も欲しいかな?
自然に、一つの点に集中できる感じが欲しい
ABC315 C
感想
今回は美味しさを最大にしたいのだからまずはソートすることを考える。
Nの大きさから$ N^2のアルゴリズムは書けない。
味の相違によって美味しさの値が異なりうるから、2つのパターンで両方探索する
味が同じパターン
味が異なるパターン
感想の感想
当たり前のものは存在しない
自分にとって当たり前のことが、当たり前ではない
当たり前だと思えなかった人に、どう伝えればいいのか考える
〇〇するだけの問題、みたいな言い方はかなりNGに感じる
比べれば良いというものではない
したいというのを常に大切に
理詰めになるのは悪い兆候
思い詰めていないか
視野狭窄
対面コミュニケーションの重要性
入力もなるべくすぐにしたほうが良い
code:NG.cpp
#include <iostream>
#include <vector>
using namespace std;
int main(void) {
int N = 0;
vector<int> l(N);
cin >> N;
}
★ 入力値によって他の入力値の制約が決まる場合があるから
code:NG.cpp
// いぇーい
入力値を配列のインデックスに使いたい場合の注意点
vectorの要素数は N + 1
for文でイテレーションする場合、1 ~ N + 1になる
<= Nでも可
解説AC
AC自体は解説なしでも出来たが、実装が間違っていた
この実装を弾くケースはなかったのかな
https://atcoder.jp/contests/abc315/tasks/abc315_c
解説AC
削除した行と削除した列の情報があれば直に探索してOK
この発想は、計算量を計算しているから出てくる
t6o_o6t.iconは、行や列を除いた新しいvectorを作ったほうが探索回数が減って良いのではないか?と思っていた
それは計算量的に必要ない
管理する必要のある情報
行、列に含まれる色の数
書けなかった部分
「行の中に、1種類しか色が存在しない」条件の表現
これは言い換えると、「行の長さと色の出現回数が等しい」
行を削除したら、各列における、その行の色の出現回数を1個減らす
削除対象行と、削除対象の色は1対1である
なぜならその行には1個しか色がないことが分かっているから
1対1なので、pairで保存するのが速い
実際に削除処理をするときに、色情報を使って出現回数を減算すれば良い
https://atcoder.jp/contests/abc315/tasks/abc315_d
解説AC
★ substrと添字アクセスの混乱を避ける
https://atcoder.jp/contests/past201912-open/tasks/past201912_f
substrが示す範囲
$ \mathrm{substr}(i, j)は、[i], [i + 1], ..., [i + j - 1]と考えると良い。
例
substr(3, 4)は、[3, 4, 5, 6]
ただし、アクセス範囲が文字列長を超えた場合にはその時点でアクセスを打ち切ってくれる。
この考え方のもと、substrの引数を修正したところ文字列の分割はうまく出来た。
isupper(s[i + j]) == trueを満たすi、jについて、substr(i, j)としていた。
しかし、大文字の部分を含めて切り取りたかった
これだとi + j - 1(大文字の1個前)までしか切り出せない
substr(i, j + 1)に修正した
これなら i + j(大文字)まで切り出してくれる
書けなかった部分
★ 大文字小文字の違いを無視して辞書順にソート
大文字を小文字に変換するには?
ASCIIコードの関係性を利用すれば書けそうだけど、バグを生みそうだった
★ tolowerで大文字を小文字に変換する
今回は特殊で、各文字列の先頭と末尾のみが大文字であることが保証できる
先頭と末尾のみを小文字に変換してソートし、出力時には先頭と末尾のみを大文字にすれば良い
解説なしAC
https://atcoder.jp/contests/abc143/tasks/abc143_c
最初に1匹のスライムがいて、2匹目から見ていったときに、1つ前のスライムと異なったらカウントを増やす
なのでカウントの初期値は1
ABC139 C
https://atcoder.jp/contests/abc139/tasks/abc139_c
正しいと思う
問題が間違っていると思うほどに、自分の実装に誤っている点が見つからない。なのに正しく動かない
このような考えにいたったときは、おそらく認識できていない挙動が存在する
デバッグ機能は悪ではないと思う
原因を考察するという目的の上では、悪ではない
ただ、改善を加えなければいつまでもデバッグ機能を使わないといけなくて、非効率的だ
★ ループ内ifで値を更新するとき、最終ループで値が更新されないことが多い
code:NG.cpp
for (int i = 1; i < n; i++)
{
cin >> b;
if (b <= a)
{
cnt++;
}
else
{
max_cnt = max(max_cnt, cnt);
cnt = 0;
}
a = b;
}
cout << max_cnt << endl;
この実装だと、最後までマスを移動できるような入力のとき、最後のイテレーションでmax_cntが更新されない
どうするのが良いのか?
毎回更新するように実装できるなら、そのほうが良い
今回の場合、max_cntを毎回取る実装にすれば簡単である
1回分例外的に処理を行う
カウント変数などを更新する処理の場合場合、初期値を工夫する
例
C - Slimes
最初にスライムが1匹存在すると考えると、例外的処理が不要。
条件分岐の中で値を更新しなければならないときに、必ず「最終イテレーションで値更新処理が含まれていないケースに到達したときに値更新をする必要があるのか」を考える
例
B - 高橋くんと文字列圧縮
最後の文字も出力に含めたい
最後の文字の処理では、1個前の文字と処理対象の文字が異なるかどうかに関わらず、出力したい
★ 最終ループの例外的処理!
解説なしAC✅
https://atcoder.jp/contests/abc116/tasks/abc116_c
★ ループ内で変数の二重定義をしない
mainスコープで定義したcntを加算していると思ったら、実際にはループ内で定義したcntを加算していた。
レベル0にするための水やりは必要ない
盲点だった
出力がすべて1多かった
レベル0の範囲は1つなので、その分が加算されていた
ansの初期値は1ではない
水やりの総数だから初期値は0
対して、各レベルの水やり範囲の数は、一番最初の要素を考慮すると、初期値を1にすると
一番最初の要素すら存在しないレベルは無視してよい
このことから、cntの初期値は1で、ループごとにansに足しあわせる実装が良い
AtCoder、提出にギャンブルみたいな緊張がつきまとうと良くない
比例してストレスが大きくなるから続かない
こういうときは、やめる
能動的学習から受動的学習に切り替える
治ったので再開
https://atcoder.jp/contests/agc040/tasks/agc040_a
方針を間違えた
前に見た典型だと思って解こうとしたら違った
C - Lower
これ
この問題で重要なのは、<が連続する場所と>が連続する場所は独立した区間として考えられるということ
<<>なら、 << と > で区切って考えれば、0, 1, 2 と 0, -1(=1, 0) になる
この考え方が非効率的なのか?
いずれの区間も、1ずつ増減していくことに違いはないので、0, 2 1, 0
<<>
<2 >1
<<>>>>
0 1 2 - 4 3 2 1 0
0 1 4 3 2 1 0
つまり区間端の処理が重要
ある区間について、
右が閉じているなら、原則 $ \frac{n(n+1)}2
右が開いているなら、$ \frac{n(n - 1)}2
この場合、$ \frac{2×1}2 + \frac{4×5}2 = 1 + 10 = 11
<>>
この場合、3
<>>><<><<<<<>>><
0 + 3 + 1 + 0 + 10 + 3 + 1 = 18
ではない
左が閉じているなら、上記の通りか?
なんか色々違うと思う
解説を読みたいけど、理解できない
AtCoder AGC 040 A - >< (茶色, 300 点) - けんちょんの競プロ精進記録
どうしてこういうふうなコードになる?
https://www.youtube.com/live/qIzuNgDV6O8?feature=share&t=134
liveだから展開できない
★ maxやminを使って、今の値でも条件を満たす場合は変更しないようにする
表現の幅が増えた
★ 条件を満たすように演算する
意味はわかったが、これで必要十分になる理由がさっぱり分からない
保留
実装
解説のまま実装する
解説AC.icon?
解説を理解できていないACを、解説ACと呼んで良いのか?
新しい知見は見つかった
★ 未解決問題:AtCoderで配列のインデックスに入力値を使ってしまう
また、配列のインデックスに入力値を使ってしまった
一度は回避できた
もう1個あるとは思わなかった
多分これを回避する方法で書く必要がある
もう1個あった
今回は直接配列のインデックスに使っているわけではなかった
G[u - 1].push_back(v - 1)
解説なしAC✅
https://atcoder.jp/contests/past202005-open/tasks/past202005_e
課題感の残るsolutionだった
鹿本のコードを読むと、--で入力値を0始まりに補正するのがテンプレートで良さそう。
原則0始まり。
機能別に整理するということ
受験勉強などでも同じことが言えるかもしれない
問題演習に習熟していることの要件として、各知識が機能・振る舞い別に整理されているということがあるのではないか
string.at はこういう動きをしたな → 使えばこの問題は問題なさそう(方針が立つ瞬間)
方針が立つためには、それを構成する要素を機能、振る舞いで連想検索できていなければならない
連想検索
連想して関係性を考え続けること
Scrapboxはちょっと足りないかな
脳の検索はもっと圧倒的なスピードで行われる
Linksを眺めて考えるのはこれに近いと思う
機能や振る舞い(=ページの内容)を抽象的に理解していることが条件
Scrapboxであれば、「このページはたしかああいう内容だったから、今考えている話に使えるな」という考え方ができる状態
解説なしAC✅
https://atcoder.jp/contests/abc061/tasks/abc061_b
t6o_o6tのAtCoder記録#64e8cf238458750000f69e9eで知った -- 法で上手く書けたよ。
解説なしAC✅
https://atcoder.jp/contests/abc163/submissions/44984780
同様。
解説なしAC✅
https://atcoder.jp/contests/past201912-open/tasks/past201912_e
★ ループ対象の配列、vector、setをループ中に変更することによるバグ
以前にも起こしたことがある。
t6o_o6tのAtCoder記録#64e0ab9484587500007b2fbe
8日前だった
1週間って意外と長い
配列に対してループを行う場合、その配列に対して要素の追加や削除をループ内で行っていないか注意深く観察する
昨日出来なかった、ABC317のCは、この問題の類題だ
https://atcoder.jp/contests/abc054/tasks/abc054_c
一筆書き問題、といえるかな
一筆書きは同じ辺を2回通らないことなので、違う
同じ頂点を2回通らない探索
どうやって管理すれば良いのかわからない。アイデアをください
↑ 解説ありAC?
https://atcoder.jp/contests/abc054/tasks/abc054_c
深さ優先探索が難しい。
なぜ、次の探索先のseenがtrueだと、探索しなくても良いのだろうか..
seenは毎回コピーするのではなく、常に同じものを使うらしい
既に探索したものをseenで管理している?
なぜそれで良い?
分かった。もし、seenを、ある頂点についての
基本的な方針は、検出 → 探索
ただ、検出するたびに探索が行われてしまうと、ループに陥る可能性がある。
ある枝の探索中は、たどってきた道や検出
解説ありAC?
seenを用いたDFSは少し手が慣れてきた
普通のseenを用いたDFSの意味も分かってきた
これで通る理由が分からない
https://atcoder.jp/contests/abc204/submissions/45007281
デバッガで実行してみようかな?
なぜseenをfalseにしないのか
多分、seenをfalseにすると、一度探索した道に迷い込んでしまうから
seenがtrueになっている場所は、少なくともDFSでは一度探索した場所
今回の要件では、一度探索した道に迷い込んでしまうとcntが増えて困る
なのでseenをfalseにしてはならない
結論
一度探索したことのある道(部分的)をもう一度たどるような道も探索に含めたい場合はseenをfalseにせよ。
seenをfalseにしてはならない場合
ある点からある点への到達可能性さえ知れればよい場合
例
C - Tour
すべての点について、到達できる点の数を知りたい。
もし一度到達した点を含む経路の情報が失われると、複数回同じ点を数えてしまう。
?
code:bad.cpp
#include <iostream>
#include <vector>
using namespace std;
vector<pair<int, int>> paths(50);
int main(void) {
for (int i = 0; i < m; i++ ) {
int a = 0, b = 0;
cin >> a >> b;
a--;
b--;
paths.push_back({a, b});
cout << pathsi.first << " " << pathsi.second << endl;
// 全部 0 0
}
return 0;
}
ChatGPTに聞いたら納得の答え
https://chat.openai.com/share/e3fbed5a-f88e-4c16-8848-21c538fb5c67
vectorを初期化するか否かは、大事な問題だな..
明日考える。
push_backと、要素数指定初期化は、組み合わせると想定しない動きを起こす。
たぶん基本的に初期化を優先したほうがいい。
原則初期化 or assign。push_backするときは、要素数指定を禁止する。
1日経った
要素数の制約が強い場合と弱い場合で立ち回りを変えよう
制約が強い
基本的に配列を使う
push_backが存在しないのでこの問題は根本的に回避できる
制約が弱い
vectorを使う
★ 競技プログラミングでグローバル変数を回避する
みなさん、競プロをするときにグローバル変数を大量に使っていませんか?実は... - 競プロをはじめた家事手伝いロボットのブログ
コメント欄にある、★ DFSをラムダ式で書くとグラフ配列をキャプチャできる
push_backは以下のときのみ許可する
グラフの辺情報を格納
解説なしAC
https://atcoder.jp/contests/abc075/tasks/abc075_c
自分で書けてとてもうれしい!!
自分で考えて、解けると、良い問題であったと感じる
考えるのを諦めて、問題のヒントを読むと、だいたい「そんなの分かるわけないじゃん..」という感想
50%以上の割合で解けるレベル帯という考えが重要
そんなの分かるわけない癖が付くと、自分で考えたくなくなる
鹿本が良かったのは、先頭から進めたことで、自分にも解ける問題をいくつも楽しめたから
DFSを理解できたかは分からないが..
DFSのテンプレの流れを上手く捉えられるようになったと思う
seenを上手く利用できて良かった!
ラムダ式で書いたDFSをラムダ式内で呼び出すには?
自動型指定子で宣言された変数は独自の初期化子では指定できません
2つの方法がある
std::functionを使う方法
諦めた
Visual Studio Codeでstd::functionがIntellisenseに認識されない
dfs関数自体を引数に追加する。
自分で書けた!✅
https://atcoder.jp/contests/abc204/tasks/abc204_c
t6o_o6tのAtCoder記録#64eb60ad8458750000f185a3
DFSの世界が見えてきた
この問題では、ある点に到達できるかを調べたい。
seenを一度trueにすれば十分と分かる。
複数回見るとTLE
もう一度、先週のABCのC問題を確認してみよう
重み付け
例のケースは全部通せた!
今まで書いてきたグラフについて、辺のもう一方の端点に加え、辺の重みも保持するようにする
pairで
こうすると、辺の重みと、辺そのものを同時に扱えて良い
一度提出してみる
dfsだから、メモ化を要求される気も。。
その前に、計算量を検証すべき
今回は、$ O(N(N+M))
間に合うような?
https://atcoder.jp/contests/abc317/submissions/45028867
ACした!
メモ化するともっと早そう
動的計画法で書ける人もいるんだろうか?
なぜWAなのか?
区間和のindexの扱いを考察する
まず、原則--で入力値を0始まりに補正するものとする。
以下は入力値を0始まりにしたものとして考える。
半開区間$ [l, r)
そのままテンプレートを適用するだけで良い。
$ S_{i+1} = S_i + v_iなので、
$ S_{n} = v_{n-1} + v_{n-2}+...+v_0
$ S_r-S_l = v_{r-1} + v_{r-2}+...+v_l
区間和を利用するときの添字について考察する
制約を見る
$ l, r\le nというふうに制約されているなら、コード中のs[l + 1]、s[r + 1]はエラーにならないはずだ
l、rは0始まりに補正しているから
解説なしAC
https://atcoder.jp/contests/typical90/submissions/45039440
t6o_o6tのAtCoder記録#64ed7c2b84587500009c6944で考察した書き方で良いと思うt6o_o6t.icon
手動テストあるある
Inputをコピー・ペーストせず、Outputをコピー・ペーストしてしまう
間違っているのは入力値なのに、ロジックを疑ってしまって収拾がつきにくい
解説なしAC
https://atcoder.jp/contests/nikkei2019-final/tasks/nikkei2019_final_a
★ intをlong longにするテンプレートを使ったほうが良いのだろうか?
変数kは、N以下である
区間の長さが任意の区間和問題では、部分和sの添字はn+1までOKであることを意識する
他の人のコードを読んでAC.icon
https://atcoder.jp/contests/joi2010ho/tasks/joi2010ho_a
考察
求めるものは、すべての移動における、「S[宿場町番号の大きい方] - S[宿場町番号の小さい方]」の和
例を使ってこれが正しいことを確かめた
移動距離は、絶対値を考えれば良い
したがって、入力される、宿場町間の距離の部分和を計算する
自分が間違っていると思えない。
思いつく可能性をすべて検証した状態。成長の機会
今回は、余りの使い方が誤っていた
自分の修正後の提出
https://atcoder.jp/contests/joi2010ho/submissions/45042408
参考にした正答
https://atcoder.jp/contests/joi2010ho/submissions/44837713
code:bad.cpp
// bad
ans += abs((sk + p % 100000) - (sk % 100000));
// good
ans += abs(sk + p - sk);
余りを取ってから差を取ると、答えが変わる??
いや、多分そうではない
以下は同じ値になる
余りを取る → 差を取って余りを取る
差を取って余りを取る
証明
今回は、計算結果をansに足している
ans も 100000 で余りを取っているからなんとなく良いと思った
★ 余りを取っても良い状況を判断する
★ 条件を満たす場合にx、そうでなければ0となる要素の配列の累積和
例
010 - Score Sum Queries(★2)
C - Attention
西向きの人の数の累積和
配列
西を向いていれば1を、そうでなければ0を足したい
部分和の表現
code:partial_sum.cpp
// good
vector<int> ve(n + 1);
vector<int> vw(n + 1);
for (int i = 0; i < n; i++) {
vei + 1 = vei + (si == 'E');
vwi + 1 = vwi + (si == 'W');
}
xが1の場合は、 == の返値を足すと綺麗
xが1以外の場合は、?
やってはいけないのは、以下のような書き方
code:paritla_sum.cpp
for (int i = 0; i < n; i++) {
if (si == 'E')
vei + 1 = vei + 1;
// ...
}
この方法だと、要素が条件を満たさないとき、対応する部分和の要素が0になる
以降の累積和が正しく足し合わされなくなる
解説なしAC(1ペナ)
https://atcoder.jp/contests/abc098/submissions/45043113
コードを見て、不安に感じたのは、minの初期値をINT16_MAXにしたこと
これは32767
多くの問題で、Nの制約は$ N \le 10^5
$ O(N^2)の提出を弾く効果がある
計算中にINT16_MAXの値を超えてしまう可能性がある
初めの方の提出でWA.iconだったので、流石にそれだけが原因ではないだろう..と思っていた
実際はこれを直してAC.iconだった
★ 最小値を求める際の初期値について考察する必要がある
C++でINT_MAXを使うにはclimitsが必要?
正直、最大値の定義はコード中で一度しかしないので、
テンプレートにしたい
bits/stdc++.hを使いたい
解説なしAC
https://atcoder.jp/contests/abc084/tasks/abc084_d
素数判定に使うのは?
方法を知らない
$ \sqrt Nまで探索
素数だったものを順番に割っていく
もしすべて余りが0ならば、素数のリストに追加
みたいな感じで良いかな?
素数判定に時間がかかった
本来の条件である、$ Nと$ \frac{N+1} 2が両方素数というのを忘れていた
ちなみに、$ N \ge 3においては、
$ \frac{2N-(N+1)} 2 = \frac{N-1} 2\gt 0より、$ N\gt \frac{N+1} 2
区間和を考えるときは必ず0始まりにする
素数判定は1始まりのほうが楽
素数判定部分と区間和処理部分を分けると、上手く行く
1始まりの処理と0始まりの処理は混合させない
解説AC
★ 小数処理の精度
変遷
WA.icon 6ケース
https://atcoder.jp/contests/abc154/submissions/45047811
WA.icon 3ケース
https://atcoder.jp/contests/abc154/submissions/45047862
float型をdouble型に変更
AC.icon
https://atcoder.jp/contests/abc154/submissions/45048034
★ 小数の出力精度を上げた
Dice in Line AtCoder Beginner Contest 154 D - はまやんはまやんはまやん を参照
感想
出力は精度よく行うべき。
printf("%.10f\n")はテンプレートにしておk
★ 小数にはdouble型を使う
精度で悩まされないためにこの選択を固定する
累積和を何も考えずに書けるようにする! - Qiita
僕自身、累積和を用いる問題に対して、毎回添字の扱いに神経を尖らせながら頑張っていたのですが、一度実装テンプレートを決め込んでしまえば何も考えなくても書けるようになりました。そうなってからは累積和を実装することにストレスが無くなりました。
とてもわかる。
フレームワーク上で各問題を思考すると、スムーズに結論が出る。
解説なしAC
https://atcoder.jp/contests/joi2011ho/tasks/joi2011ho1
二次元累積和を使う必要がありそう。
IceとOceanの、各行における累積和を計算してやっていたがTLE
最大で$ 10^8回程度のループなので、この処理では間に合わない
累積和を何も考えずに書けるようにする! - Qiita
を読みながら直しているけど、二次元累積和が何を意味しているのか..
デバッガで、生成した二次元配列を読むと、少し理解できた
配列の作り方も分かった
配列の造りが分かったら、使い方も分かった
https://atcoder.jp/contests/joi2011ho/submissions/45050202
解説AC
https://atcoder.jp/contests/agc024/tasks/agc024_a
table:hoge
k 1 2 3 4 5 2n 2n+1
A B + C C + A + A + B (2A + B + C) 2A + 3B + 3C 6A + 5B + 5C 10A + 11B + 11C
B C + A A + B + B + C (A + 2B + C) 3A + 2B + 3C 5A + 6B + 5C 11A + 10B + 11C
C A + B B + C + C + A (A + B + 2C) 3A + 3B + 2C 5A + 5B + 6C 11A + 11B + 10C
1, 2, 3, 6, 11, 22, 42
table:hoge
k A B C
5 10A+11B+11C 11A+10B+11C 11A+11B+10C
6 22A+21B+21C 21A+22B+21C 21A+21B+22C
$ a_{n+1} = b_n+c_n
$ b_{n+1} = a_n + c_n
$ c_{n+1} = a_n + b_n
$ a_{n+2} = b_{n+1}+c_{n+1}=2a_n+b_n+c_n=a_{n+1} + 2a_n
$ a_{n+2}-a_{n+1}-2a_n =0
特性方程式 $ x^2 -x-2 =(x+1)(x-2)=0を解いて、$ x=-1, 2
ゆえに、$ a_{n+2} a_{n+1} = a_{n+1}-a_{n}
解き方思い出せない
もう1回、高校数学をやる必要があるかもしれない
こういう根本的な部分でつまずいてしまうから、世間一般的に、大学受験で失敗している人間は扱いにくいのだろう
★ 「A - Bの推移」を、「具体的な数字を使って」、把握する
A - Bの推移
= 問題文で問われていることに注目して
具体的な数字を使って
= 文字だけを使うことに執着せずに、具体例を活用する
試してみよう
試せた ↓ いい感じ
table:hoge
k 0 1 2 3 4
A 2 5 9 19 37
B 1 6 8 20 36
C 4 3 11 17 39
A-B 1 -1 1 -1 1
このように、$ (A_0 - B_0)(-1)^kとなる。
kが偶数のときは A - B。
kが奇数のときは B - A。
鹿本に学びを得た。
以下のような方針で問題ないのか。
次に光るボタンのvectorを用意する。
ボタン1(添字0)からvectorをたどっていく。
ループ判定?
ボタン1からのボタンのたどり方はただ一通りである。
ただ一通りであることを示す。
もしただ一通りでないとしたら、
https://scrapbox.io/files/64eec9be7a197c001c853a5d.png
こうなる
これがおかしい
枝分かれが存在するはずである。
すべてのボタンについて、そのボタンを押したあとに光るボタンは一種類しか存在しない。
ゆえに、あるボタンから押し始めたとき、以降の押す順番は一通りしか存在しない。
ボタンnを押すと光るボタンは、ボタンn+1しか存在せず、ボタンn+1を押すと光るボタンは...
したがって、以下のような構造以外は存在し得ない。
https://scrapbox.io/files/64eecb6edc3c3e001cfc5726.png
ループ判定をすればよいと分かる
ループ判定は、「次に光るボタンが1である」かどうかを見れば良い
先に述べた通り、ボタンを押す順番は一通りしか存在しないから、ループするならいずれ1にも戻ってくる
方針が立った
vectorに入力する
1からたどる
次に光るボタンが1か?
seen配列を更新するのを忘れた
色々更新しなければならない
ボタンを押した回数
既にたどったノードのリスト(seen配列)
現在見ているボタンのindex
2021年に何か解いていた
2021
https://atcoder.jp/contests/abc065/submissions/21984516
1511ms
2023
https://atcoder.jp/contests/abc065/submissions/45291473
17ms
早くて良いね。
解説(人のコード)AC✅
https://atcoder.jp/contests/abc053/tasks/arc068_a
任意の面を置いて..
この時点で、6が側面に来るようにしなければならない
1だと反対の面に来るのでいけない
2 ~ 5
2から始める場合
nに対して、nと7 - n以外で最大の整数を取れば良い
1. 2に対して、2と5以外で最大の整数は6
2. 6に対して、6と1以外で最大の整数は5
3. 5に対して、5と2以外で最大の整数は6
4. 6に対して、6と1以外で最大の整数は5
このように、2から始めると、そのとき取るべき戦略は一意に定まる
2n - 1回目の最大得点は、6(2n - 1) + 5(2n - 2) = 22n - 16?
ここで式を表すことが苦手な模様
分かった
n = 2m のとき、11m
n = 2m + 1のとき、11m + 5
パターン化したい
数学Ⅰに戻りたい..
基礎的能力の低さ
5から始める場合
1. 5に対して、2と5以外で最大の整数は6
同じことになった
ここでは、n回目の時点で取りうる最大値$ h_nについて、$ h_{n-1} \lt x \le h_n (n\ge 2)
この条件を満たすなら、n+1回目の操作が不要となる
条件を言い換えるには、場合分けが必要
$ n = 2mのとき、$ n-1 = 2m-1より、
$ 11m-6\lt x\le 11m
$ \frac x {11} \le m \lt \frac{x+6}{11}
この条件を満たすmを見つけたら、2mが求めるべき値。
$ \frac x {11}を切り上げた数
$ n = 2m+1のとき、$ n - 1 =2mより、
$ 11m\lt x \le 11m+5
$ \frac {x-5}{11}\le m\lt\frac x {11}
この条件を満たすmを見つけたら、2m+1が求めるべき値。
方針が出来た
答えが合わない原因が分からない
6を入力したときには、2と出力
$ n\ge 2という成立条件を満たしていないから不正入力
分からない
149696127901への出力が27217477802
これは1多い
code: NG.cpp
#include <iostream>
#include <cmath>
using namespace std;
int main(void)
{
double x = 0;
cin >> x;
cout << (long long)min(2 * ceil(x / 11), 2 * ceil(x - 5 / 11) + 1) << endl;
return 0;
}
他の人の解答を読んだ
解説を読んでいないが、おそらく次のように考えている人が多い
https://scrapbox.io/files/64f8980364775c001bdf73a7.png
1引いて11で割って切り捨てて、2をかける
1引いて11で割って、余りが5以下(6未満)だったら1足す
余りが6以上だったら2足す
感想
式を表して不等式を作り、条件をプログラム可能にするのが難しかった
共通テスト、数Ⅰ、選択問題5
これは複雑なので、手法の1つ程度に考えておくべき
シンプルにできる捉え方があるなら、覚えておこう
★ 入力と出力の関係を図にする
解説AC
https://atcoder.jp/contests/abc165/tasks/abc165_d
これは、数学的に考察できることが1個ありそう(未来透視)
$ p =\mathrm{floor}(\frac{Ax}B),q =A\cdot\mathrm{floor}(\frac x B)
$ \frac{Ax} B\le p \lt \frac{Ax} B + 1
$ A\frac x B \le q \lt A(\frac x B + 1)
$ -A\lt p - q\lt 1
このように考えたが、間違っているのか?
床関数なのに、天井関数になっている
これだと全部1になってしまうが..
数学的に考察できることが1個ありそう(Take 2)
$ \frac{Ax} B-1\lt p \le \frac{Ax} B
$ A(\frac x B - 1) \lt q \le A\frac x B
$ -1\lt p-q\lt A
最大値はAなのか?
でもAだったら、そういう問題にはならないはずだ
解説を読む
AtCoder ABC 165 D - Floor Function (茶色, 400 点) - けんちょんの競プロ精進記録
解説中の表を見て、t6o_o6tのAtCoder記録#64f8ab258458750000e93bfdは必要条件だということが分かった
xによっては、A - 1にならないことがある
それを調べるべきだった
★ 不等式変形による数学的考察で発見できるのは必要条件の可能性
★ 必要条件を満たさないケースの存在が確実にある
でも、たしかに$ 0\le p-q \le A-1は常に満たしている
★ 直感的に動きが想像できなければすぐに手を動かす
数式を使った方法は意味が分からなかった
理解できてきた
次に同じことを考えられるようにするには、以下のようなことを思い出す必要がある
あまり$ rと割る数$ aの関係は、$ r\lt aである
例えば、5で割ったなら余りは当然5未満になる
床関数にあまりと割る数を用いた数を渡すとき、$ \lfloor \frac r a \rfloor = 0
上述の大小関係を、$ rの定義時に書き添えておくことで、この事実に気づけるはず
整数$ nについて、$ \lfloor n\rfloor =nである
ガウス記号の中に整数を見出す
割られる数$ xとあまり$ rの関係は、$ r\le xである
例えば、8を割ったなら余りは8以下
加えて、以下のことを思い出せるとなおよい
$ \lfloor x \rfloor \lfloor y \rfloor \le \lfloor xy \rfloor
補助的に、今後以下のように定める
あまりは$ rとおく。
OK導けた!
$ \lfloor \frac {Ar} B \rfloor
$ 0\le r \le N
$ 0\le r \le B-1
後は、$ \min(N,B-1)を取るだけ
自分で導けて楽しかった!!
AC.icon!
https://atcoder.jp/contests/abc165/submissions/45312553
解説なしAC
https://scrapbox.io/files/64f9e584498c38001ce4ea8a.png
j回目に訪れた町indexを保存する
これを行う根拠
2回以上同じ町indexが現れた(seenがtrue)だったときに、何回の周期でその町が現れるかが分かる
とりあえず、書いた
今は完璧だと思っている
1つめのケースに対する問題
★ for文で回しているのにカウンタ変数を++していないか確認する
AC.icon
https://atcoder.jp/contests/abc167/submissions/45313747
3年前にはWAしているし、遅かったから今は成長した
2つめのケース
デバッグをし始めてから非常に時間がかかった
「現在いる町」を、初期値(0)にセットすると上手く動くが、そうでない場合は上手く動かない
これは安定しないので、つねに0で良い
単純な文法ミス
k - p / tではなく(k - p) / tだった
★ 足し引きするときには()が必要、とまで考えても良い
自分で導いたindexの計算式は間違っていないことがわかったので、一安心
この問題はt6o_o6tのAtCoder記録#64eec9148458750000add453と骨子がまったく同じだと最初に見て気づいた
実装も、以前リンク先の問題を解いたときと同じ seen を用意する方針から考えていった
★ 巡回のループを見つける問題
seenと現在地のほかに、もう1つや2つ情報の更新があるので、忘れないように
テンプレートを持っておいても良い
区間和やグラフDFSのように、indexに困らずに済むはず
解説AC
7, 77, 777
第n項は、$ \sum^{n}_{i=1}7\cdot10^{n-1}=7\cdot\frac {10^n-1} 9
良さそう!
この数がKの倍数 → Kで割って余りが0
この問題は、数Bが必須
AtCoderにおける数学の位置づけを思い知ったかもしれない
では、ないと思う
具体的にどこまで探索すれば良いのか?
ここが自分の数学的な考察の限界
この次は、手を動かして探索する
Kが7の倍数の場合、Kを7で割った数に対して考えると良さそう
$ \frac {10^n - 1} 9について考える
素因数分解に法則性を見出したかったが、無理だった
一応、11..11の素因数分解は数学のトピックの1つである
解説をちょっとだけ読んだ
★ 鳩の巣原理
教育的な問題だ
「第n項までのn個の数に、Kで割った余りが0になる数が少なくとも1つ存在する」
これは、鳩の巣原理から、余りが0にならないことを確認しながら第K項まで順番に見ていけば確実にどこかで見つかる
t6o_o6tのAtCoder記録#64fb25b3845875000024e4d4
最悪$ 10^6桁になると思う。100000桁を扱える型とは?
この考え方では詰まった
余りの性質を使う
$ 10^{100000}のような値に対する対処は、解説PDFにあった
解説動画を見た
★ 一般的な制約のKでmod Kを取りながら計算してもよいなら値を抑えられる
漸化式に余りを適用していく際のグラフは、有向
枝分かれがなく、同じところを二度通ったら、以降は巡回する
★ 巡回の開始と終了の位置を知る問題は、t6o_o6tのAtCoder記録#64f9e5aa8458750000e70038
説明
この問題は、各数に余りを計算しなくてもよくなっている
★ 余りを計算する数を漸化式で表せるとき、漸化式の前項に対するmodに同じ計算を施してmodを取っても関係性が保てる
modで計算し続け0になったら、元の式でも対応する数をKで割った余りは0になる
重要な考え方として、mod漸化式で、ある項に対する次項は必ず同じになる
一度ループが発生すれば(=第n項と第m項でmodが一致)、それ以上異なるmodは出現しなくなる
★ Kで割った余りは0からK-1のいずれかになる
第K項まで、余りが0になるかを見ていって、もし一度も0にならなければ必然的にループが発生している
一度でも0になったらその時点でループを打ち切ればよい
ABC319 D
コンテスト中は方針がまったく立たなかった
★ 二分探索の書き方が分からない
注:ACできていない
ARC113 B
答えの書き方を間違えた
x = ((x % 10) * (x % 10)) % 10
これだと、同じ数を掛けられない
累乗でもmodの性質が成り立つことを見つけた
ここで改めて、数Ⅰが必要
loopの省略方法は?
型を決めこみたい
今のところ、その数をいつ見たのか?を格納している
もう一度発見したら、以下のように計算できる
周期:今のカウント変数 - 前回いつ見たのか
https://scrapbox.io/files/64fd7dec8bea44001ba75b43.png
t6o_o6tのAtCoder記録#64f9e5aa8458750000e70038でも同じ考察をした
Teleporterの解説は、もう少し単純だったかも?
図において緑色の部分だけ調べれば良くなる
1. 周期を見つける
2. 周期が始まったタイミングを思い出す
3. 一番最後の周期が終わる場所を計算する
4. それ以降を順番に見ていく(あるいは、index指定で過去の結果を呼び出す)
過去の結果を呼び出したほうが早いから、過去に現れた順番を配列に保存するべきだろう()
配列の要素数は、たかだか10個でいい。
0123456789という10種類が最大で存在し、11個目を見るときには必ず被っているから
t6o_o6tのAtCoder記録#64f9f5fe8458750000e70045と同じ問題を起こした
https://scrapbox.io/files/64fd82b0c82d78001ce375cc.png
$ k - p - \lfloor \frac {k -p } t \rfloor tは、そこから消し去るべき要素の数なんだ
省略した要素を「なかったことにして」考える必要がある
なので、1個戻って、新しくカウントを始めよう
上図では、「1」から1個戻って「0」からカウントを始める
周期内の領域において、1個目の要素は「7」となる
https://scrapbox.io/files/64fd86c8a53e8c001bc6f868.png
1 1の場合は?
つまり、同じ数が連続する場合
図(誤り)
https://scrapbox.io/files/650117e6a6d1f8001cffb6ef.png
図(正しい)
https://scrapbox.io/files/650118fc4f8e8f001b6dc70a.png
この図を見て、kの定義がおかしいと気づいた
今回はindexの計算をしたい
indexの範囲が必要
値の個数は必要ない
まとめ
k を個数として考えない。範囲という考え方も持つと良くなると思う
一部ケースでWA.iconになることもあるが、そのときはまず制約を疑う
型ごとの初期値
intは0、boolは..
関数内では初期化されない?
今後は配列にも初期化を行うように({}とすれば、補完してくれる)
初期化しないという選択肢はない。
解説なしAC
AB Substrings
何度かWA、REを出している。注意が必要かもしれない
Move and Win
Flipping Signs
Select +/- One
Walking Takahashi
解説ありAC
Poisonous Cookies
面白い問題
未AC
Same Integers