−目次
AtCoder Beginner Contest 242 F,G問題メモ
最近のABCのCとかD、普通にむずいんだよな
F - Black and White Rooks
問題
- N×M マスの盤面上にブラック飛車 B 個とホワイト飛車 W 個を置く
- 配置ルール
- 1マスに2個以上の駒を置かない
- 違う色の飛車同士は互いの利きにいない(同じ色はOK)
- 同じ色の駒は互いに区別しないとき、置き方の個数を 998244353 で割ったあまりで求めよ
- 1≤N,M≤50
解法
まず、1つの行や列に、黒と白両方が置かれることはない。
「●・・●・・・○」みたいな配置を考えたとき、 混在していると必ず黒と白の境界が存在するので、その部分の駒が互いの利きに入ってしまう。
なので、行と列をそれぞれ、以下のグループに分ける。
- 黒のみ置かれる
- 白のみ置かれる
- どちらも置かれない
「黒のみ置かれる行数 Rb」「黒のみ置かれる列数 Cb」「白のみ置かれる行数 Rw」「白のみ置かれる列数 Cw」として これらを決め打ったとき、その行・列を N×M のどこに配置するかは、以下のようになる。
- N!Rb!Rw!(N−Rb−Rw)!M!Cb!Cw!(M−Cb−Cw)
また、以下がわかれば、
- fb(Rb,Cb)= その行数・列数内での黒い駒 B 個の置き方
- fw(Rw,Cw)= その行数・列数内での白い駒 W 個の置き方
全体の置き方の個数は、これらの積となる。
- N!Rb!Rw!(N−Rb−Rw)!M!Cb!Cw!(M−Cb−Cw)×fb(Rb,Cb)×fw(Rw,Cw)
fb,fw をDPで求める
Rb,Cb,Rw,Cw の全探索は O(N2M2) で可能なので、あとは fb,fw を前計算などで O(1) で得られればよい。
重複を避けるため、fb,fw は必ず空行・空列のない置き方で無いといけない。
B=3 Rb=3 Cb=2 OK NG NG 1つめのNGの置き方は、 ●・・ ●・・ ●●● Rb=2 Cb=2 を探索したときに既に数えられている ・●● ●●・ ・・・
重複を気にしなければ、Rb×Cb 個のマスの中から置く B マスを選べば良いので、Rb×CbCB。
そこから空になってしまう行数・列数を Rx,Cx と決め打てば、除外すべき置き方はどの行・列を空にするかを含めて、以下のようになる。
- RbCRx×CbCCx×fb(Rb−Rx,Cb−Cx)
これを 0≤Rx≤Rb,0≤Cx≤Cb(ただし Rx=Rb かつ Cx=Cb を除く)の範囲で全探索し、
Rb×CbCB から除外すれば、fb(Rb,Cb) が求められる。
これも O(N2M2) で計算できる。
包除原理で求める
何の個数を (−1)k の k とすればよいか少し迷うが、「必ず空行・空列にすると定める行数+列数」とできる。
空行があってもよい条件でそれぞれの場合を求め、
- N!Rb!Rw!(N−Rb−Rw)!M!Cb!Cw!(M−Cb−Cw)×Rb×CbCB×Rw×CwCW
その上で (N+M−Rb−Cb−Rw−Cw) の偶奇によって答えに足す正負を切り替えると、最終的な答えが求まる。
これなら前計算は必要なく、Rb,Cb,Rw,Cw の総当たりのみで済む。
相変わらず、包除原理はなかなか直感的には見えづらい。
G - Range Pairing Query
なんでこんな解かれてるの?
問題
- 長さ N の数列 A1,...,AN が与えられる
- Q 個のクエリを処理せよ
- l,r が与えられる
- Al,Al+1,...,Ar の中で取れる「同じ数字のペア」の最大個数を求める
- 1≤N≤105
- 1≤Q≤106
- 実行制限時間: 5sec.
解法
TLE解法
SegmentTreeなどでは解きづらい。
1種類の値 x に限定すれば、SegmentTreeを使って区間 [l,r] に含まれる x の個数を取得し、
2で割って切り捨てることで x で作れるペア数は O(logN) でわかるが、
それをAi の値の種類数… O(N) 回繰り返さなければならない。全部で O(QNlogN) で余裕のTLE。
1つの値を1bitで管理してXORでマージすれば、「ペアにできない x が存在するかどうか」がわかり、 popcountして区間長から引くことでペア数もわかるが、それもbit長が O(N) になり、XORの演算に時間がかかるため無理。
公式解説の解法
Mo's Algorithm、クエリ平方分割を使う。なんか名前だけ聞いたことあった。
以下のような場合に使える。
- クエリを先読みできる
- 区間を1つ伸ばしたり縮めたりすることによる答えへの影響が簡単に計算できる
今回の場合、現在の区間内の各値の個数(の偶奇)を管理しておけば、以下の要領で区間を1つ伸縮したときの答えを更新できる。
- 現在の個数が奇数である値 x を追加する→答えが1増える
- 現在の個数が偶数である値 x を削除する→答えが1減る
何らかの順序でクエリを並べ替え、その順に区間を伸縮させて答えを求めていくことを考える。
例えば r の昇順でクエリをソートし、この順に区間を伸縮させて答えを求めていくと、
右端の伸縮は最小限となるが、左端の伸縮で毎回 O(N) かかってしまう場合がある。
1 |-| 2 |----------------| 3 |-| 4 |-------------------|
そこで、l を、なんか適当なブロックサイズ B でグループ分けする。
その中ごとに r の昇順でクエリを処理すると、左端の伸縮も1クエリ当たり最大 O(B) で収まる。
<-B-> 1 |-| 2 |---------| 3 |--------| 4 |-----------------|
分割した分、この処理をグループの個数 NB 回繰り返す必要があるが、
- r を毎回左から右まで動かす計算量: O(NNB)
- l を1クエリ毎に行ったり来たりさせる計算量: O(QB)
全体の計算量はこの和となるので、B=N√Q 程度に設定すれば O(N√Q) となる。
本問題の制約では最大 108 となるが、、、 まぁ r を動かす範囲は右のグループほど狭まるなどの定数倍の削減があり、間に合う。