Loading [MathJax]/jax/output/CommonHTML/jax.js

AtCoder Beginner Contest 386 E,F,G問題メモ

E - Maximize XOR

問題文

  • 長さ N の非負整数列 A および整数 K が与えられます。ここで二項係数 (NK)106 以下であることが保証されます。
  • A から異なる K 項を選ぶとき、選んだ K 個の数の総 XOR としてあり得る最大値を求めてください。
  • すなわち max1i1<i2<<iKNAi1Ai2AiK を求めてください。

制約

  • 1KN2×105
  • 0Ai<260
  • (NK)106
  • 入力は全て整数

解法

N,K は大きいが、二項係数の値に制約があるという珍しい問題。
K の(直接的な)制約に騙されず、実はごく小さい値しか取れないことを見抜ければ、全探索が間に合うと分かる。

二項係数の意味するところの1つである「N 個の中から K 個選ぶ場合の数」の、 この「選んだ具体的な K 個」の組が 106 通り以下であると言っている。

よって、「K 個組を全列挙する」「1組当たり O(K) で実際にXORを計算する」が 現実的な時間でできるなら、実際に全列挙して最大値を求めればよい。

前者の計算量は難しいので保留して、 後者は O(K(NK)) が実行制限時間に収まるためにはせいぜい K20 くらいまでが限度である。

ここで、(NK)106 の範囲で K が大きくなるケースを考えてみる。

(200000199999) みたいに N,K がともに大きいケースは、 選ばない方を列挙することで (2000001) として扱える(KNK にできる)。
KNK を前提とすると (NK) が小さくなるのは (2KK) のような形であるが、 それでも小さい方から確認していくと K=12(2KK)>106 となり超えてしまう。
よって、実は K11 が保証されていることが分かる。

前者の計算量はちゃんとはわからんけど、まぁ K11 ならそんなひどいことにはならんやろ!と信じて投げると通る。


前者、(NK) の全列挙は、再帰関数によるシンプルな実装で O(K(NK)) でできるらしい。

ただ、終了条件をサボる実装だとTLEするらしく、なかなか難しい。

Pythonなら、itertools.combinations を使うのが楽。

Python3

F - Operate K

問題文

  • 文字列 S に対して以下の操作を 0 回以上 K 回以下行って、文字列 T と一致させられるか判定してください。
    • 次の 3 種類の操作のうちひとつを選択し、実行する。
      • S 中の (先頭や末尾を含む) 任意の位置に、任意の文字を 1 つ挿入する。
      • S 中の文字を 1 つ選び、削除する。
      • S 中の文字を 1 つ選び、別の 1 つの文字に変更する。

制約

  • S,T は英小文字からなる長さ 1 以上 500000 以下の文字列
  • K1K20 を満たす整数

解法

操作はレーベンシュタイン距離の操作と同じである。
2つの文字列 S,T のレーベンシュタイン距離は O(|S||T|) で求められる。
今回は当然それでは間に合わないが、代わりに「K 以下か、より大きいか」だけ判定できればよく、K は小さい。

ここで、以下で定義されるDPのうち、

  • DP[i,j]:=S の先頭 i 文字と T の先頭 j 文字の距離

|ij|>K になるような箇所は明らかに距離が K 以上となるため、埋める必要は無い。

|ij|K となる箇所だけ埋めていけば、O(K(|S|+|T|)) で求められる。

G - Many MST

問題文

  • 正整数 N,M が与えられます。
  • 頂点に 1 から N までの番号がつけられている N 頂点の重み付き完全グラフであって各辺の重みが 1 以上 M 以下の整数であるようなものは MN(N1)/2 通りあります。
  • それぞれについて、最小全域木に含まれる辺の重みの和を求め、それらの総和を 998244353 で割った余りを出力してください。

制約

  • 2N500
  • 1M500
  • 入力は全て整数

解法

問題の言い換えを重ねてDPに持っていく。ある程度は典型ではあるが、言い換えの連続が難しい。

最小全域木の言い換え

最小全域木のコストは、プリム法やクラスカル法のように、小さい辺から採用していくアルゴリズムが簡単だが、 今回は複数のグラフの最小全域木を同時に求めないといけないため、使いづらい。

他の言い換え方法として、以下がある。

  • 辺重みが 0m1 のグラフ G を考える。
    • k=1,...,m に対して「重みが k 未満の辺のみを残したグラフ」を Gk とする
    • その連結成分の個数を c(Gk) とする
    • 最小全域木のコストは、mk=1(c(Gk)1)

辺の重みを1階層ずつに分解するイメージ。
例えば重み 5 の辺が最小全域木に使われるとして、 それによって結ばれる2つの連結成分を考えると、 k=15 に対しては結ばれないので連結成分数は各「1」だけ多くなり、 それ以上に対しては結ばれる。合計「5」だけ全体に寄与することになる。

つまり、MN(N1)/2 個ある全てのグラフの集合を S として、 GSmk=1(c(Gk)1) が答えとなる。

今回は辺重みが 1M だが、以下のようにすればよい。

  • とりあえず 0M1 の答えを求める。
  • 1つの全域木には N1 個の辺があり、各辺のコストが1ずつ小さい場合が求まっている。
  • 求める最小全域木は全部で MN(N1)/2 あるので、全体に (N1)×MN(N1)/2 加算すれば、1M の場合の答えとなる。

GSmk=1(c(Gk)1) の定数部分を外に出すと、 GSmk=1c(Gk)M×MN(N1)/2 となる。

最終的に、GSmk=1c(Gk)+(NM1)×MN(N1)/2 を求めればよい。

後半は簡単に求まるので、GSmk=1c(Gk) を求められればよい。

主客転倒

Σの順番を入れ替え、k 毎に GSc(Gk) を考えることにする。

主客転倒し、「ある特定の頂点集合 T が、Gk において1つの連結成分を構成する」 (そして答えに1寄与する)ような GS がいくつあるか?を考える。
2N1 個ある全ての頂点集合について、それを T としたときの結果を合計すれば、GSc(Gk) が求まる。

|T| が同じなら求めたい値も一緒なので、集合のサイズだけが重要となる。
s=1,2,...,N 毎に「fk(s):= 頂点集合 {1,2,...,s}Gk において1つの連結成分となる G の個数」を求めれば、その結果をそれぞれ (Ns) 倍すればよい。

で、その求め方だが、

(a)     (b)    (c)
○--○        ○  ○    (a): Tに属する頂点同士を結ぶ辺を表す
|×| <---->   ○      (b): TとT以外を結ぶ辺を表す
○--○        ○  ○    (c): T以外同士を結ぶ辺を表す
  • (a) s 個の頂点をコスト k 未満の辺で連結にしつつ、残りの辺のコストも決める方法の個数
    • ちょっと難しいので、とりあえず gk(s) と置く
  • (b) 特定の s 個と残りの Ns 個を結ぶ s(Ns) 本の辺のコストの決め方の個数
    • Gk において非連結なので、全てコスト k 以上でないといけない
  • (c) 残りの Ns 個同士を結ぶ辺のコストの決め方の個数
    • これは自由

gk(s) が求まれば、fk(s)=gk(s)×(Mk)s(Ns)×M(Ns)(Ns1)/2 で求められる。

gk(s)s の小さい方から求める。
まず、gk(1)=1 である。

s2 に対しては、

  • (a) 内の全ての辺を自由に決める方法の個数は Ms(s1)/2
  • そのうち、連結成分が2つ以上に分かれるような方法の個数を引く。
    • 頂点 1 を含む連結成分のサイズが tt=1,...,s1)となるような決め方の個数は、上記の(a)(b)(c)と同様の考え方により、(s1t1)×gk(t)×(Mk)t(st)×M(st)(st1)/2
  • gk(s)=Ms(s1)/2s1t=1(s1t1)×gk(t)×(Mk)t(st)×M(st)(st1)/2

これで、O(N2)gk を求められる。

k に対して繰り返し、O(N2M) で全て求められる。

Python3

programming_algorithm/contest_history/atcoder/2024/1228_abc386.txt · 最終更新: 2025/01/06 by ikatakos
CC Attribution 4.0 International
Driven by DokuWiki Recent changes RSS feed Valid CSS Valid XHTML 1.0