atcoder典型90問を日々解いていく
その過程で, 必要があればテンプレートを更新していく
まずは★5以下のものを順番に解いていきます. その後, ★6, ★7にチャレンジします.
答えでないかどうかの判定が高速にできる場合は, 答えで二分探索する
小さい規模のものは全探索する
最短距離計算を2回行う
- ある頂点から他の頂点への最短距離を求める
- 最も距離が長かった頂点を基準に, 他の頂点への最短距離を求める
何度も同じデータを参照する場合は, 事前計算しておく
- 事前計算で縦の和, 横の和を計算しておく
貪欲に1文字ずつ決定していく
- 何度も同じデータを参照しないように, 各アルファベットがそれ以降でどのポジションにあるのか事前計算しておく
ソートして挿入位置を二分探索で検索する
漸化式を立ててDPで解く
- 事前に関係ない文字は除外しておく
- 入力された文字列を後ろから参照していく
- 漸化式 (文字に対応する数字をa:0, t:1, c:2, o:3, d:4, e:5, r:6として, 後ろからi番目の文字がkに対応するとき)
- k=6のとき dp[k][i+1] = dp[k][i]+1
- k<6のとき dp[k][i+1] = dp[k+1][i]+dp[k][i];
累積和の差で解く
UnionFindで解く
スタート地点からのダイクストラとゴール地点からのダイクストラで解く
それぞれソートして差をとる
無駄を省いた全探索をする
- 制約「合計9999枚以下の硬貨でちょうどN円を支払うことができる」も見逃さないようにする
逆三角関数を使って角度を求める
- atan2(y, x)
桁数に注意してlong longで受け取り, logの中身を整数で比較する
強連結成分分解を使って, 頂点をお互いに行き来できるグループに分ける
強連結成分分解のアルゴリズム:
- DFSで頂点をたどり, 帰りに訪問したことにする (すべてたどれなければ訪問できなかった頂点からDFSを再開する)
- 辺の向きをすべて反転させ, 1.の訪問と逆順にDFSを行う (それぞれのDFSでたどれた分が強連結成分となる)
3辺の最大公約数を求める
- g = gcd(a, b, c) = gcd(gcd(a, b), c)
- 切る回数は (a/g - 1) + (b/g - 1) + (c/g - 1)
ステップ数の偶奇性に注目する
- 2つの数列の差が与えられたステップ数Kより大きいなら一致不可能
- 差がK以下なら, +/-を交互に行うことで残りの手数を消費できる (偶数ならちょうど消費できる)
DFSで訪問順に交互に色を塗っていく
集合を使うだけ
領域にすべての点をメモしておいて, 最後に累積和で重なっている数をセルに記録する (いもす法)
- 1001 x 1001の2次元配列aを用意
- 与えられる点の範囲が0から1000であることに注目
- 与えられた長方形の点(lx,ly), (rx,ry)に対して次のように記録
a[lx][ly]++
a[rx][ry]++
a[lx][ry]--
a[rx][ly]--
- 長方形領域ごとに毎回加算するとTLEになってしまうことに注意
- 行方向の累積和をaに記録後, 列方向に累積和をaに記録
- これで各セルに重なっている長方形の数が記載される
遅延評価付きのセグメント木で解く
エラトステネスのふるいで解く
空間が小さいため, 全探索する
- 全パターンを試すときにnext_permutationを使っても良い
コーナーケース(例外)に気をつける
単調性を利用して1長さを減らしたり増やしたりする (尺取り法というらしい)
点を45度回転させると考えやすい
DPで解くが, 最大値を求めるときにセグメント木を使う
lcm(a, b) = a * b / gcd(a, b) オーバーフローに注意して, しきい値より大きいかの判定には除算を使う
各辺を何回通る必要があるかを考える
- 深さ優先探索で各部分木に頂点がいくつあるかを調べる
- それぞれの辺は頂点を2つの集合に分けるため, 1で求めた頂点の数からその辺を通る回数を求める
「9の倍数 = 各桁の和が9の倍数」という性質を使う. Kが9の倍数でなければゼロ, Kが9の倍数なら各桁の和がKとなる数をDPで求める
dequeを使った01BFSで, コストが小さいところからたどっていく
- 位置だけでなく, たどってきた向きも記録しておく
- 迷路の周りを壁で囲んでおくと, はみ出るかどうかの処理が楽になる
- priority_queueを使ったダイクストラでもOKだが計算コストは少し増える
シフトした数を覚えておいて, 実際にはシフトしない
mod 46で考えて, それぞれの数列で個数をカウントして, 全探索(46^3)する
1分かけて部分点b
がとれて, さらに1分かけると(満点a-部分点b)(<a/2)
がとれることを利用して, ソートして上位k個を選ぶ
漸化式を立ててDPで解く
全探索2^40ではTLEになるので, 商品集合を半分に分けてそれぞれ全探索2^20した2つの結果を統合する
- それぞれの商品集合で, 個数ごとに価格リストを昇順に持っておく
- 結果の統合には, 片方の集合の価格から見て, もう一方の集合の価格リストを二分探索して個数を求める
式をかいてsumの位置を移動させる
- たとえば3つのサイコロなら,
sum{i0}sum{i1}sum{i2}A[0,i0]A[1,i1]A[2,i2] = sum{i0}A[0,i0]sum{i1}A[1,i1]sum{i2}A[2,i2]
全探索する. 積がオーバーフローしないように適宜mod pで考える.
DPでS円になるか判定し, DPテーブルを逆順にたどって解を求める
周期性を検出して省略する
最長増加部分列を左右から利用する
長い配列を用意して中央から埋めていくようにする
- c++のdequeはランダムアクセスがO(1)でできるらしいので, dequeを使っても良い
行が8行以下という制約から, 行の選択に関して全探索して, 同じ値のみがある列から最も多い値のものを選ぶ
差分だけを考えると, 更新する箇所は各クエリに対して2箇所のみ
- たとえば,
|a-b|+|b-c|+|c-d|+|d-e|
とあって, b,c,dに+vすると|a-b-v|+|b+v-c-v|+|c+v-d-v|+|d+v-e|
=|a-b-v|+|b-c|+|c-d|+|d-e+v|
期待値の線形性(和の期待値は期待値の和)を使って, それぞれの2つの期待値の和を計算する
8進数から10進数に直し, 10進数を9進数に変換する
- 長さLの8進数文字列S → 10進数 N
N = sum_i S[L-i-1] * 8^i
- 10進数 N → 9進数文字列S
N!=0
の間繰り返すS = "N%9" + S
N/=9
- クエリを先読みして, 辺をつなげておく
- 未確定な値をゼロで仮に埋めておいてすべての値を確定させておく ー 実際に値が与えられたら, その差分を計算し, ノードが奇数個離れているか偶数個離れているかで値の差を足すか引くか決める
- その時点で与えられている情報で値が確定できるかどうかは, 連結判定を別で持っておく
- 連結判定はUnionFindかsetのlower_boundを使うと可能
- s.lower_bound(x)を使わず, lower_bound(s.begin(), s.end(), x)にするとTLEになるため注意
高速べき乗法を使う
x軸, y軸それぞれ独立に考えて, 中央値を求める
- 差の絶対値が最小になる点は中央値
マップが小さいので再帰関数で全探索する
- 実装上楽になる工夫: マップの周りを壁で囲むことでindexがはみ出るかの判定を省ける
DFSで木DPで解く
dp[now][連結成分のラベル集合]=部分木で条件を満たすものが何通りか
- nowのラベルがaのとき {a} を生成するのは
- 辺(now, child)を削除する場合, 部分木が条件を満たすために
dp[child][{a,b}]
- 辺(now, child)を削除しない場合, {a}だけにならないといけないため
dp[child][{a}]
- すべての子でこれらのパターンがあるため,
dp[now][{a}] = prod(dp[child][{a}] + dp[child][{a,b}])
- 辺(now, child)を削除する場合, 部分木が条件を満たすために
- nowのラベルがaのとき {a,b} を生成するのは
dp[now][{a,b}] = すべての場合 - dp[now][{a}]
すべての場合 = prod(dp[child][{a}]+dp[child][{b}]+2*dp[child][{a,b}])
- ※
dp[child][{a,b}]
は辺(now, child)を削除する場合も含むため係数2がつく
素因数分解を行い, log2(素因数の数)回行えば良いとわかる
円形を回避するために2回配列を並べて累積和をとり, それぞれのピース位置から始めたときに終了時点を2分探索する
与えられた辺を順に見ていき, 頂点ごとにカウントするだけ
左上から順番に処理していく
身長体重を二次元配列上の点としてカウントし, K x Kの長方形領域を全探索でずらしていく
- 長方形領域内の和は, 行方向の累積和, 列方向の累積和の順で事前計算をしておくと, 以下の値が長方形領域内の和になる
cumsum[i+k][j+k]-cumsum[i-1][j+k]-cumsum[i+k][j-1]+cumsum[i-1][j-1]
桁数ごとに, 書きこむ回数を求める
- 等差数列の和
- 1 + 2 + ... + N = N(N+1)/2
- L + ... + R = n(L+R)/2
- 項数: n = R-L+1
- aのmod pにおける逆元
- a^(p-2) mod p
- p: 素数
- フェルマーの小定理から a * a^(p-2) = 1 (mod p)
- a^(p-2) mod p
- unsigned long long (ULL)
- ULLの最大値は18446744073709551615 (20桁)
- LLの最大値は9223372036854775807 (19桁)
- オーバーフロー対策としてULLを使うか, 2で割る際に場合分けが必要
i文字目以降にどの位置に別の文字が出るかを記録しておくことで計算可能
- もしくは, 余事象を考えて2種類の文字を含まない総数を数えてもよい
約数列挙して全探索する
- オーバーフローに注意
ビットごとにbit全探索をする
- i桁目を取り出す:
(A>>i)&1
全頂点間の距離を求め, 答えXで二分探索する
- 全頂点間の距離: Floyd–Warshallで求める
- dijkstraで1対他をN回やってもよい
- 二分探索: lower_boundとupper_boundを求める
- Xが上昇すると到達可能なペア数が減るため, 広義単調減少
- lower_boundとupper_boundの差が答えになる