Codeforces#616-div1-C: Prefix Enlightenment

これが解けないのは悔しいんだけど、まあ難しいね...。

問題

codeforces.com

考察と解法

 まず、どの3つのスイッチを取っても、その3つに関わるランプは存在しないという条件に注目する。これは、各ランプについて、押すと影響のあるスイッチは高々2つしかないことを言っている。そうなると、ランプを辺、スイッチを頂点としたグラフを考えたくなる。とりあえず、すべての辺が2つの頂点を結んでいる場合を考えよう。
 このグラフの頂点(スイッチ)に黒か白の色を塗ることを考えよう。最初はすべての頂点は白色で、これはスイッチがoffであることを表す。黒色に塗ると、これはスイッチがonであることを表す。このように考えると、辺(ランプ)iについて、そのランプが点灯しているときは、以下の制約を満たす必要がある。

  • S_i = 0のとき、辺の両端の色は異なる
  • S_i = 1のとき、辺の両端の色は同じ

この制約を満たすように色を塗るとき、黒色の頂点を最小で何個塗ればよいかということを答えよというのがこの問題の要求となる。
 さて、すべての頂点が白い状態から、番号がi以下の辺が制約を満たすように黒く頂点を塗っていくことを考えよう。ある頂点vを黒色に塗ることにすると、そのスイッチを繋ぐ辺のうち番号がi以下の辺があれば、上の制約より、辺が繋ぐのもう一方の頂点の色が決定することになる。この決定は連鎖するので、i以下の辺でつながれた連結成分ごとに状態が決まっていくことになる。頂点vを塗らないことにしても、連鎖は同様にしておこる。つまり、取り得る状態は連結成分について高々2つしかなく、このうち黒い頂点の数が少ない方がわかればよいことになる。この計算が高速に行えればよい。
 制約の要となるグラフの辺をどんどん増やしていきながらクエリに答える問題なので、Union-Findを使った解法を考えるのは不自然ではないだろう。ここでは、頂点を倍加するテクニックを用いる。つまり、グラフの頂点として、(v,白)と(v,黒)という2K個の頂点を用意し、最初は辺が1本もない状態からスタートする。また、頂点を黒く塗ることにはコストがかかるので、(v,黒)に重み1を載せておく。辺iを追加するとき(頂点u,vを結んでいるとする)は、次のような併合操作を行う。

  • S_i = 0のとき、(u,白),(v,黒)を併合して、(u,黒),(v,白)を併合する
  • S_i = 1のとき、(u,白),(v,白)を併合して、(u,黒),(v,黒)を併合する

併合のときに、連結成分内の重みの総和も更新する。すると、辺(u,v)の属する連結成分の答えは、(u,白)が属する連結成分の重みと、(u,黒)が属する連結成分の重みであることがわかる。両者は同じ連結成分に属することはない。今回は問題の入力として必ず条件を満たすスイッチの押し方が存在することが保証されているので、矛盾が生じる(前述したように、あるスイッチの状態を決定した後に、状態の決定が連鎖していく中で黒かつ白である必要のある頂点が現れること)ことがないからである。この処理を辺1から順に行い、答えを適宜更新してくことでこの問題が解ける(実装の項参照)。
 ある辺が1つの頂点としか結ばれていないときの扱いは、あらたにスイッチXを用意し、(X,白)と(X,黒)という頂点を用意して同様の処理を行うことにする。ここで、(X,黒)のコストを5000兆などの十分大きい数にしておけば、Xが黒く塗られるようなスイッチの押し方が最適解となることがなくなるのでうまくいく(結局、2K+2個の頂点を用意することになる)。

実装

 辺(u,v)を追加する手順は

  • u,vが同じ連結成分にあったら何もしない(対称性により、色に関係なく同じ連結成分にあるかどうかが決まる)
  • そうでないとき、uが属する2つの連結成分の重みのうち、小さい方を答えから引く。vについても同様。
  • u,vを前述のルールによって併合する
  • u(v)が属する2つの連結成分の重みのうち、大きい方を答えから引く。

実装は適宜関数化するとよい(snukeさんの実装を参考にした)。 codeforces.com