AtCoder NoviStepsを埋めてみる(2) 貪欲法 2Q問題の続きです。ちょっと難しめの問題を考えます。

E – Divide Graph

E – Divide Graph

問題の概要

N 頂点 M 辺からなる単純連結無向グラフ G が与えられる。
辺 i は頂点 U[i] と頂点 V[i] を結んでいて辺の重みは 2 の i乗 である。
G の連結成分の個数がちょうど 2 になるように、G の辺のうちいくつかを選んで削除する。
そのとき削除される辺の重みの総和の最小値は? 998244353 で割ったときの余りを答えよ。

辺のないグラフを構築して辺の重みが大きい順に辺をつないでいきます。ただし辺を繋ぐことで全体が連結になってしまう場合はその辺は繋ぎません。この処理は UnionFind木を使えばできます。

辺の重みは 2 の i乗 なので、辺 i の重みは i – 1 番目以前のすべての辺の重みの総和よりも大きいです。なので上記の方法で正しい解を得ることができます。

Dsuクラスは ac-library-csharp のものを使っています。

参考: ac-library-csharpを使ってみる

D – Match, Mod, Minimize 2

D – Match, Mod, Minimize 2

長さ N の非負整数列 A, B と 正整数 M が与えられる。
A をならべかえて (A[i] + B[i]) Mod M の総和を最小化したい。
最小値はなにか?

Mod M を考慮しなくてよいのであればどのように並べ替えても総和は変わりません。また A[i], B[i] は M 未満なので A[i] + B[i] が M を超えることはあってもその 2 倍を超えることはありません。

A[i] + B[i] が M を超えることで M だけ総和が減少します。この問題は

長さ N の非負整数列 A, B と 正整数 M が与えられる。
A をならべかえて (A[i] + B[i]) ≧ M となるペアをできるだけたくさん作りたい。
この個数は?

という問題を解ければ解くことができます。

E – Simultaneous Kagamimochi

E – Simultaneous Kagamimochi

長さ N の整数列 A が与えられる。すべての要素が 1 以上であり、すでに昇順でソートされている。
A[i] * 2 ≦ A[j] となるペアをできるだけたくさん作りたい。
ぜんぶでいくつできるだろうか?

A[0] とペアにできるもので最小であるものを探し、次にペアとなっていない最小のものから…と同じことを繰り返すのは残念ですが、嘘解法です。

以下のコードでは出題ページにある 3 つの入出力例 では正しい解を返しますが、提出してみると WA になります。

嘘解法

正しい解法は以下のとおりです。

K 個のペアが作れるときは 上位 K 個 と下位 K 個を組み合わせればできる。
K の最大値が問題の解なので、これを二分探索法で探す。

二分探索法の処理は ac-library-csharp の StlFunction.BinarySearch メソッドを使っています。

E – Takahashi is Slime 2

E – Takahashi is Slime 2

問題の概要

縦 H 行横 W 列のマス目があり、マス(i,j) には強さ S[i, j] のスライムがいる。
マス(P,Q) にいるスライムは自身の強さの 1 / X 倍未満のものを選んで吸収し、その強さだけ強くなる。
また吸収されたスライムがいた部分は吸収したスライムによって埋められる。
このような処理を繰り返した場合、最初にマス(P, Q)にいたスライムの強さの最大値はどうなるだろうか?

一度接したスライムは吸収しない限り接し続けることと、自身の強さが減少することはないことから、吸収できるスライムはすべて吸収してしまってかまいません。このような処理は接しているスライムのうち、最も強さの小さいスライムを吸収し続けることで実現できます。PriorityQueue を使いましょう。

注意点として 一度 PriorityQueue に格納したマスは再度格納してはならないことと、自身の強さの 1 / X 倍未満かどうかを判定するときにそのまま割り算してしまうと誤差で WA になる場合があることです。

long a = 9007199254740993;
long b = 16384;
long c = 549755813888;

このとき 1.0 * a / b を計算すると c と一致しますが、実は両者は同じではありません。両辺に b を掛けてから a と c * b を比較すると 1 違うことがわかります。

なので割り算ではなく掛け算で比較します。このとき乗算に伴うオーバーフローが予想されますが、すべてのスライムの強さの総和は Math.Pow(10, 18) を超えないので、吸収できるか判定対象となるスライムの強さ × X が Math.Pow(10, 18) を超える場合は吸収できないことがわかります。

C – Socks 2

C – Socks 2

問題の概要

i 番目の組は色 i の靴下 2 枚からなる N 組の靴下がある。
配列 A で与えられる色の靴下を 1 枚ずつなくしてしまった。
残りの靴下でペアを作りたいが 2 枚の色の差の総和ができるだけ小さくなるようにしたい。
差の総和の最小値は? 残りの靴下が奇数枚の場合、1枚は使われることはない。

残りの靴下の色が格納されている昇順でソートされた配列 B をつくります(同じ色が 2 枚ある場合は同じ値を 2 回格納する)。

B の長さが偶数の場合は隣り合う要素の差の総和を計算すればそれが解となります。奇数の場合はどれか 1 枚を除外して同じように差の総和を計算すればいいのですが、この処理はどうやって高速化すればいいでしょうか?

まず先頭から隣り合う要素の差を計算して、その累積和を計算します。次に後方からも同じように後方からの累積和を計算します。あとは両者の組み合わせから 1 枚だけ取り除いた 2 枚の色の差の総和の最小値を得ることができます。これらのなかから最小のものを探せばよいです。

E – Takahashi Quest

E – Takahashi Quest

問題の概要

N 個の出来事がおきる。

t[i] = 1 のとき、タイプ x[i] のポーションを 1 つ発見する。
このときはこれを拾うか何もしないかの選択をする。

t[i] = 2 のとき、タイプ x[i] の敵と遭遇する。
もしタイプ x[i] のポーションを持っている場合、それを 1 つ消費することで敵を倒すことができる。
持っていない場合は敵に敗北する。

すべての敵を倒すことはできるだろうか? できない場合は -1 を出力せよ。
できる場合は途中で持っているポーションの個数の最大値 K を最小化しようとしたとき、その値は何になるだろうか? また t[i] = 1 のすべてのときにポーションを拾うかどうかについて出力せよ。

ポーションを見つけたとき、それを使用するかどうかはそのときにはわかりません。そこで敵に遭遇したときに過去に遡って必要なポーションを見つけたときはこれを手に入れるようにします。また手持ちのポーションは少ないほうがよいので過去に遡るときは現在に一番近い時間にします。

各種類のポーションをみつけた時刻をスタックに格納すれば、現在にもっとも近い時間がわかります。各時間においてポーションの増減を記録する配列(ポーションを拾う:1, ポーションを使う:-1, なにもしない:0)を用意しておくとそこから所持するポーションの最大値を計算することができます。

D – Printing Machine

D – Printing Machine

問題の概要

N 個の商品がベルトコンベア上を流れている。ベルトコンベアには印字機が取り付けられており、商品 i は今から T[i] μs後に印字機の範囲に入り、その D[i] μs後に印字機の範囲から出る。

この印字機は、1 μs おきにその範囲内にある商品 1 つに一瞬で印字ができる(印字機の範囲に入る瞬間や範囲から出る瞬間を含む)。この印字機は最大で何個の商品に印字することができるだろうか?

印字機の範囲内に複数の商品がある場合、範囲外へ出る時刻が迫っているものから印字すべきです。そこでそのようなシミュレーションをおこないます。T の範囲が最大で 10^18, 印字機の範囲内にある時間も 10^18 ですが、印字機の範囲内に商品がない場合は時刻を先に進めてしまえばシミュレーション可能です。

F – Vouchers

F – Vouchers

問題の概要

N 個の商品が売られていて 商品の定価は P[i] 円です。
M 枚のクーポンがあり、i 枚目のクーポンは定価が L[i] 円以上の商品に対して使うことができ、定価より D[i] 円安く買うことができる。
複数の商品に同じクーポンを使ったり、同じ商品に複数のクーポンを使うことはできない。
すべての商品を買うのに必要な最小の金額は?

値段の安い順に商品を見ていき、使えるクーポンがあるならばその中で最も D[i] が大きいクーポンを使用して商品を買えばよいです。PriorityQueue を使えば解けます。