−目次
AtCoder Regular Contest 199 (Div. 1) A,B,C問題メモ
A - Flip Row or Col 2
問題文
- N×N 行列 A=(Ai,j) (1≤i,j≤N) および長さ N の整数列 R=(R1,R2,…,RN),C=(C1,C2,…,CN) が与えられます. A の各要素は 0 または 1 であり,R,C の各要素は 0 以上 N4 未満です.
- あなたは好きな行・列を選んで、その行・列全体をフリップする(0と1を置き換える)ことができます。
- フリップする行と列をうまく選ぶことで操作後の A が以下の条件を満たすことが可能かどうか判定してください。
- 各 i=1,2,…,N に対して,A の i 行目の要素の総和 N∑j=1Ai,j は Ri である.
- 各 j=1,2,…,N に対して,A の j 列目の要素の総和 N∑i=1Ai,j は Cj である.
- 可能ならばフリップする列と行の組の一例を出力してください.
- T 個のテストケースが与えられるので,それぞれについて答えてください.
制約
- 1≤T≤105
- 1≤N≤1000
- Ai,j∈{0,1}
- 0≤Ri,Cj<N4
- T,N,Ri,Cj は整数
- 1 つの入力ファイルに含まれる N2 の総和は 106 以下
解法
Ri,Cj<N4 という制約がどう見ても際立っているのだが、どう活かせばよいかわからなかった。
実は、R,C との合致以前に、そもそも行・列フリップを繰り返して作れる「全ての行・列について総和が N4 未満」となるような盤面は、高々1通りしかない。
よって、「全ての行・列について総和が N4 未満」という状態を1つ作れれば、 それが R,C と合致するかを調べればよい。
具体的な構築方法も、N4 を上手く使わないとなかなか思いつきづらいが、
まず、行に操作して、1列目を全て $0$ にする。 10101 01010 11110 00001 01111 → 01111 10011 01100 00110 00110
こうすると、今後、行について操作するのは N4 回未満に限られる。 (1列目が N4 以上になってはいけないので)
よって、この時点で1列に'1'が N2 個以上ある列は反転させなければいけないし、N2 未満の列は反転させてはいけない。フリップする列が一意に決まる。
vvv 01010 00100 00001 01111 01111 → 00001 01100 00010 00110 01000
その後、N4 個以上ある行をフリップすればよい。
A が「全ての行・列について総和が N4 未満」とすることが可能であれば、そのようになっている。
00100 00100 01111 10000 00001 → 00001 00010 00010 01000 01000
B - Adjacent Replace
問題文
- 長さ N の非負整数列 A=(A1,A2,…,AN) および非負整数 K が与えられます.
- 以下の操作を 104 回以下行うことで A1=K にすることが可能かどうか判定してください.
- 整数 i (1≤i≤N−1) を選ぶ.x=Ai⊕Ai+1 としたとき,Ai,Ai+1 をそれぞれ x で置き換える.
- ここで,⊕ はビット単位 XOR 演算です.
- 可能な場合,そのような操作手順を一つ出力してください.
- T 個のテストケースが与えられるので,それぞれについて答えてください.
制約
- 1≤T≤50
- 3≤N≤60
- 0≤Ai,K<260
- 入力は全て整数
解法
基本的な方針は早々に分かって、後は場合分けを頑張るだけやし時間かければいけるやろ!で挑戦したけど、 個々の場合分けに対する実装が遅すぎて間に合わなかった。どうやって素早く実装できるの。。。
使うindexの決定
まず、隣接は関係なく「A から好きな要素を抽出して、総XORで K を作ってください」という問題は典型であり、 XOR基底を求めることで可能不可能を判定できる。
基底を求める際に、「この基底は、元の A のどれとどれをXORして作られたもの」という情報も計算しておけば、 具体的な構築方法の一例も求められる。
これにより、そもそも A をどのように組み合わせても K を作れない場合は“No”。
以下、作れるとする。
上記の方法で見つけた K の構築方法の1つを I=(i1,i2,...,im) として元の問題に戻ると、 「操作を繰り返して、A1 を、Ai1⊕Ai2⊕...⊕Aim にする」とよいことがわかる。
じゃあ、実際に可能か?を考える。
使わない要素が2つ連続している箇所がある場合
a b c d e f o: 使う o x x o x o x: 使わない 構築方法の一例 a b c d e f x a bc bc d e f 同じ箇所に2回操作することで x 隣接2要素を0にできる a 0 0 d e f x dをずらして d,f の間を2つ以上開ける a 0 d d e f xx 2回の操作で0にする a 0 d 0 0 f x x x 右から順に a 0 df df f f x x 右から順に adf adf df df f f
使わない要素が2つ連続している箇所があったら、その2つを0にして、使う要素を移動できる。
他に使わない要素が1つしか連続していない箇所があっても、↑の移動で順にずらして、2つ以上の連続にすることができる。
使う要素が2つ連続している箇所がある場合
a b c d e f o: 使う o x o o x o x: 使わない 構築方法の一例 a b c d e f ともに使うc,dを1つにまとめると、 x どちらか一方は「使わない要素」と見なして良くなる a b cd cd e f xx a 0 0 cd e f 後の流れは、1つめの例の3行目以降と同様
最初、使わない要素が2つ連続してる箇所が無い場合でも、使う要素が2つ以上連続してる箇所があれば、 その連続箇所の端の2要素を1つにまとめると、使わない箇所の2つの連続を作ることができる。
どちらも無い場合
a b c d e f o: 使う o x o x o x x: 使わない or x o x o x o
使用・不使用が最後まで交互に並ぶ状態のみ該当する。
この場合は不可能。どうしても初手で o と x が混ざり合ってしまい、それを分離する方法が無い。
ただし、I の他にも K を作り出せるindexの組がある場合は、そちらでできる可能性がある。
他に K を作り出せる方法があるかどうかは、XOR基底に“0”が存在する(という言い方は厳密には正しくない? まぁ、掃き出し法の過程で最後に余った0が出てくる、という意味)のと同値。
“0”を作るためのindexの組の1つ J=(j1,j2,...) と、I の、集合としてのXOR(片方にのみ含まれる要素からなる集合)が
I の他に K を作り出せる方法の1つとなる。
ただし、作り方が2通りあっても、どちらもダメ、という場合もあるので注意。
6 21 1,4,16 か 2,8,31 でしか 21 を作れないが 1 2 4 8 16 31 どちらもダメ
不可能な状態は2通りしかないので、XOR基底に“0”が2個以上存在すれば、4通り以上の K を作る方法があり、必ず可能な状態にできる。
高々2通りの J を試せばよい。
構築方法
まず、コーナーケースとして、使う要素が0個の状態を考える。
これはつまり、K=0 ということなので、i=1 に2回操作すれば簡単にできる。
以降、使う要素は1個以上あるとする。
K を作るための要素 I=(i1,...,im) に対し、右端の Aim から降順に、I に含まれるindexの要素だけをXOR合成しながら A1 まで運んでいきたい。
ひとまず、A1 まで運ばずとも「使う要素を全て合成し、i1 の位置に K を作る」ところまでを目指す。
そのために、「im の右に、使わない要素(バッファ)が2個以上ある」初期状態を目指す。
使わない要素が1個しか連続していない箇所を上手く乗り越えるには、右側に2個のバッファを要するからである。
なお、間が0個、2個以上、の場合はバッファは不要。
右から合成していくための初期状態として目指す形 ○ ... o x x ] o: 最も右で使う要素 × ..... o x ] ]: 配列の終わり × ....... o ]
b を a にXOR合成するために、その間に使わない要素が何個あるか、で場合分け 0個 ... a b そのまま操作すればよい ... ab ab 1個 ... a x b x x a,b:使う要素 ... a x b 0 0 x:使わない要素 ... a x b b 0 ... a 0 0 b 0 b の右にバッファが2個あれば ... ab ab b b 0 b を、間にある x を含めず a と合成できる 2個以上 ... a x x x b 間の使わない要素を全て0にした上で、 ... a 0 0 0 b 降順に操作して b を運んでいける。 ... ab ab b b b
最初から右側にバッファが2個以上あればよいが、そうで無い場合、事前に調整して作っておく必要がある。
調整の方法を考える。
使う要素のうち、最も右にあるもののindexを im とする。バッファが2個未満という前提で、im=N−1 または im=N である。
最も右にある使わない要素の連続箇所を u、使う要素の連続箇所を v とする。
どちらも存在しない場合はあり得ない。(使用不使用が交互に並ぶ不可能配置となり、判定の時点ではじかれているはずなので)
・u が存在する場合: u 以降の使う要素を、左から順番に左に寄せていける。 ... x x a x b x c a,b,c: 使う要素 ... 0 0 a x b x c x: 使わない要素 ... a a a x b x c ... a 0 0 0 b x c ... a b b b b x c ... a b 0 0 0 0 c ... a b c c c c c ←最も左以外のcは使わない要素なので ... a b c x x x x ←このように見なせ、条件を満たす。 なお、a,b,c をどこまで左に移動させるかは、 上記の例のように「隙間無く並べる」だけでなく 「この時点で合成までしてしまう」「それぞれ2個ずつ左に寄せる」などでもよい。お好みで。 ・u が存在せず、v が存在する場合 ・v が、最も右の使う要素を含む場合 ・右にバッファが1個ある場合 ... a b x ] ... ab ab x ] ←この、右側のabは使わない要素なので ... ab x x ] ←このように見なせ、条件を満たす。 ・バッファが0個で、使う要素が3つ以上連続している場合 ... a b c ] ... a bc bc ] 末尾から順に操作していけばよい。 ...abc abc bc ] ...abc x x ] ←このように見なせ、条件を満たす。 ・バッファが0個で、使う要素の連続が2個のみの場合 ... x b c ] ... x bc bc ] ちょっとややこしいが、 ... 0 0 bc ] 一旦右にまとめてから左に移動させる ... bc bc bc ] ... bc x x ] ・v が、最も右の使う要素より左にある場合 ... a b x c x d ... ab ab x c x d ... ab x x c x d ←このように見なせるので、uが存在する場合に帰着
これで事前調整して、末尾から I に含まれるindexだけを合成していき、i1 の位置に K を作れたとする。
これを i=1 まで運ぶ作業も、だいたいは上記と同じ感じで、
i1=1 既に完成している i=2 x o x x ... N=3 の時は判定問題の時点で不可能とはじかれているため、 x o 0 0 ... N>=4 で、使う要素の右側には2個以上の使わない要素の連続がある。 x o o 0 ... 0 0 o 0 ... 一旦右に待避させることで、 o o o 0 ... 左側に2個以上の使わない要素の連続を作る。 i1>=3 x x x o ... 2個以上の使わない要素の連続があれば、 0 0 0 o ... 操作を繰り返して0にできるので、 o o o o ... 後は降順に操作して、使う要素を運んでいける。
これで完成である。
操作回数を見積もる。以下の場合にかさみそう。
- 「u が存在する場合」で、u が A の左端にあり、各要素を左端にはるばる集める場合
- ほとんど交互で、右端だけバッファが2個ある状態
前者は、使用不使用が交互に並ぶ中の使う要素(N2 個程度)の各 Ai それぞれについて、O(i) 個の使わない区間を 0 に舗装してから O(i) かけて運ぶ必要がある。定数倍を考慮すると、2i+2 以内には可能なので、これを飛び飛びで総和を取っていくと、だいたい N22 程度と見積もれる。
後者は、1つ使わない要素を乗り越えるのに 8 回の操作が必要なので、4N 程度と見積もれる。
いずれにしても、十分余裕を持って 104 回以内に収めることができる。
C - Circular Tree Embedding
問題文
- この問題では,頂点に 1,2,…,N の番号が付けられ,辺に番号が付いていない N 頂点の木を単に N 頂点の木と呼ぶことにします.
- N 頂点の木 T および (1,2,…,N) の順列 Q=(Q1,Q2,…,QN) が次の条件を満たすとき,順列 Q は木 T の 良い順列 であると呼びます.
- 円周上に N 個の点 1,2,…,N がこの順で反時計回りに等間隔で配置されている.
- 木 T の各辺 {u,v} に対して 2 点 Qu,Qv を結ぶ線分を書き込む.
- このとき,書き込まれた N−1 本の線分のうちどの 2 本を選んでも,それらは端点を除いて共有点を持たない.
- M 個の (1,2,…,N) の順列 P1=(P11,P12,…,P1N),P2=(P21,P22,…,P2N),…,PM=(PM1,PM2,…,PMN) が与えられます.
- P1,P2,…,PM が全て良い順列となるような N 頂点の木の個数を 998244353 で割った余りを求めて下さい.
制約
- 2≤N,M≤500
- P1,P2,…,PM は (1,2,…,N) の順列
- 入力は全て整数
解法
問題設定を理解するのがまず難しい。
満たすべき条件
頂点番号は 0-indexed に読み替える。
各 P の逆順列(Q[Pi]=i となるような Q)を求めて、Q の順に円周上に並べる。
i 0 1 2 3 4 5 6 7 P 5 2 4 6 7 0 1 3 Q 5 6 1 7 2 0 3 4 ↓ 他のPも同様に ⑤ ⑥ ③ ④ ⓪ ② ④ ① ② ⑤ ③ ④ ③ ⑦ ⓪ ⑦ ⑦ ⑤ ⓪ ② ⑥ ① ① ⑥
N 頂点の木に対して「円を1つ選び、周上の対応する番号の位置に頂点を置いていく」操作を考える。
この時「どの円を選んで操作しても、辺が交差しない」ような木の個数が答えとなる。
ひとまず、最大の⑦を根とした木で考える。
(通常は⓪を根とすることが多いが、今回は後述のDPで⑦を根とした方がやりやすい)
条件を満たす木は、「木の全ての部分木について『含まれる頂点集合がどの円周上でも連続している』」ことが必要十分条件となる。
⑦ 部分木の頂点集合(1頂点のみのを除く) / \ ②⓪③ ⑤ ⑥ ⑤②⓪③④ / \ | ⑥① ② ④ ① ⑦⑤②⓪③④⑥① /\ これらは全て、どの円周上でも連続している ⓪③ (集合内での順序は円ごとに変わってよい) →この木は条件を満たす
ある部分木をなす頂点集合 U の間に、部分木の外にある頂点 v があり連続が途切れていたら、 v は U のどの頂点とも結ばれない(それ以外の頂点と結ばれる必要がある)のに、U 同士は結ばれる必要があり、どうしても交差してしまう。
U = {0,1,2,3}, v = 4 : : ⓪①②③が部分木を為す場合、 ⓪--------③ どうしても(⓪①)と(②③)の間は結ばれることになり、 ①----②' ④は他の頂点に結びに行けなくなる。 ④
数えあげ
ある部分木に対し、含まれる頂点集合がどの円周上でも連続しているものを「条件★を満たす」とする。
また、ある「条件★を満たす部分木」の集合のうち、どの円周上でも含まれる頂点が連続しているものを「条件☆を満たす部分森」と呼ぶことにする。
たとえば上例の木では、②⓪③と④はそれぞれ条件★を満たす部分木で、 さらに②⓪③④も各円周上で連続しているので、{②⓪③, ④}は「条件☆を満たす部分森」である。
根を除く N−1 頂点集合から作れる「条件☆を満たす部分森の個数」が答えとなる。
この森のそれぞれと根を結ぶことで木が完成する。
(全 N 頂点からなる条件★を満たす部分木を数えると、構造が同じでも根を区別するため、重複が発生する)
順列上で連続していることが条件であることから、区間DPで数え上げることを考える。
ただ、「区間」が何を示すものなのか、まだちょっと曖昧である。
各順列 Q に以下の改変を行う。
- 1つめの順列を、頂点を置換して、⓪①②…⑦ の順にする
- 残りの順列にも同じ置換を施した上で、⑦が最後になるように巡回シフトする
i 0 1 2 3 4 5 6 7 P1 5 2 4 6 7 0 1 3 Q1 5 6 1 7 2 0 3 4 ↓ 置換 Q'1 0 1 2 3 4 5 6 7 Q2 3 4 5 7 1 6 0 2 ↓ 同じマッピングで置換 6 7 0 3 2 1 5 4 ↓ 7が末尾になるように巡回シフト Q'2 0 3 2 1 5 4 6 7
1つめの順列をこうすることで「条件★を満たす部分木」となるためには「頂点番号が連続するものからなる」が必要条件となり、 「区間 [l,r)」を「頂点番号 l,l+1,...,r−1 の頂点集合」と自然な形で定義することができる。
また円環で考えると、⑦の位置が中ほどにあると順列の端を跨ぐ部分の処理に困るが、
端にすることでそこで切り開いて数列と考えてよくなる。
以下、円周上での連続は、数列上での連続という判定に置き換えられる。
まず、以下を求める。
- ok[i,j]:= 値 i,i+1,...,j−1 が、全ての順列上で連続しているか(bool)
これは、全ての順列の全ての区間について調べて間に合う。
まず、ok[i,j] を全て True で初期化する。
また、着目中の順列 Q′ における値 k のindexを、Rk とする。
順列 Q′ 上で (l,l+1,...,r−1) が連続しているかの判定を行いたい場合、
max(Rl,Rl+1,...,Rr−1)−min(Rl,Rl+1,...,Rr−1)=r−l−1 かどうかを調べればよい。
違反していたら ok[l,r] を False にする。
各 l から r=l+1,l+2,...,N と1つずつ拡張していけば、1つ区間を伸ばすのに O(1)、全体 O(N2M) で調べられる。
これをもとに、以下のDPをおこなう。
- DP1[l,r]:= 区間 [l,r) の頂点からなる、条件★を満たす部分木の個数
- DP2[l,r]:= 区間 [l,r) の頂点からなる、条件☆を満たす部分森の個数
- それぞれは、部分木の根がどれかも区別する。
初期状態は以下のようにしておく。
長さ0と長さ1の区間は、1通りであることは自明なので、最初から埋めておく。
- DP1[i,i]=DP1[i,i+1]=1
- DP2[i,i]=DP2[i,i+1]=1
- その他は0
長さ2以上の区間 [l,r) について更新を考える。これより短い区間のDPは全て埋まっているとする。
ok[l,r]=True であれば、単一の部分木が作れる。
根を m=l,l+1,...,r−1 とした場合の部分木の個数は、DP2[l,m]×DP2[m+1,r] となる。
また、複数の部分木からなる部分森については、1つめの部分木がどこまで続くかを全探索する。
- DP1[l,r]=r−1∑m=lDP2[l,m]×DP2[m+1,r](※ok[l,r]=True の場合。Falseの場合、0)
- DP2[l,r]=DP1[l,r]+r−2∑m=l+1DP2[l,m]×DP2[m,r]
としてDPを埋めていき、DP2[0,N−1] が答えとなる。