裏紙

ほぼ競プロ、たまに日記

CF 847 J - Students Initiation

問題

Problem - J - Codeforces

問題概要

n頂点m辺の無向グラフが与えられる。この各辺について、どちらかに向きを付けなければならない。その時に各頂点からの出次数の最大値が最小になるような割り当て方を答えよ。

  •  1 \le n \le 5000
  •  0 \le m \le min(5000, n(n-1)/2)
  • 多重辺や自己ループはない

イデア

各辺について、端点のどちらかに+1をする時に、頂点の持つ値の最大値を最小化するというように見ることが出来る。答えを二分探索することを考える。各頂点はxまでは増やすことを許すとする。このxが妥当かどうかをフローを使って解く。フローを考えるグラフは次のように構築する:

  • sourceから、edge_iへ容量1
  • edge_i (= (x_i, y_i))から、V_{x_i}V_{y_i}へそれぞれ容量1
  • V_iから、sinkへ容量x

このようなグラフを構築し、最大フローを求める。その値がmに一致すればOKとなる。このフローは、Dinicを使えば高速に動作する(|V| = O(n), |E| = O(n)のとき、Karzanov's theoremにより、DinicはO(n \sqrt{n})で動作するらしい(?)。この辺は理解しきれてない)。

復元は、残余グラフを見れば簡単にできる。

実装(C++)

続きを読む

Codechef October Challenge 2017 - Shooting on the array

問題

Contest Page | CodeChef

問題概要

長さnの数列aが与えられる。xy平面を考えて、数列に対してi(1 \le n \le n)番目の要素について、(i,0)(i,a_i )を端点とする線分を引く。以下の2種類のクエリが合計でQ個与えられるのでそれを処理せよ:

  • + i X :  a_i += X
  • ? i L R : x軸に平行な光が、R-L+1個発射されると想定する。j(1 \le j \le R-L+1)番目の光は、座標 (i-0.5 , L+j-1.5)からx軸正の方向に向けて発射されるが、一度線分にぶつかると光は遮断される。このとき、光が当たっている線分の個数を答える。

2つ目のクエリについては、光はクエリごとに独立で考える → 毎回このクエリごとに光源を設置して、このクエリに答えたらその光源はなくなる。

  •  1 \le n \le 10^6
  •  1 \le a_i \le 10^9
  •  1 \le Q \le 10^5
  •  0 \le X \le 10000
  •  1 \le L \le R \le 10^9

イデア

まず、2つ目の幾何的なクエリは分かりにくいので、幾何的な要素を除外する:

i \le jであるjについて、高さ k-0.5 (L \le k \le R)の光が当たるということは、a_j \ge kであり、 i \le l \lt jについて、 a_l \lt a_jである必要がある。このことから、光がj番目の線分にあたるための線分の条件は、

  •  a_j \ge L (これ以上無いとそもそも光が当たらない)
  •  a_l \lt a_j (i \le l \lt j)
  •  max(a_l) \lt R (i \le l \lt j) (これがないとj番目の線分に光が届かない)

なので、これを満たすようなj (i \le j)の個数を数える、ということになる。

3番目の条件はR \le a_hを満たす最小のh (i \le h)を見つけ、h\lt jの部分については考えないということにすれば良いので、i \le j \le hについて考えることで、3番目の条件を除外できる。hを見つけることを考えると、maxを取るsegment tree上を二分探索することでO(logn)で求めることが出来る。

以下、1,2の条件のみを考える。Rはもう関係ないので、今segment tree上で [l , r)にパラメータLという3つ組のクエリに答えることを考えよう。この区間に対して、L = -1というパラメータが与えられた場合の答えをxとして持っているとする。

区間の要素が1つだけならこのクエリに対する答えは自明で、L以上なら1になる。

区間の要素が2つ以上ならば、2つの区間に分けて再帰的に解いていくことになる。左側、右側のそれぞれのノードに対する答えをx_{left} , x_{right}とする。左側の子ノードの区間に対して、その区間の最大値で以下のように場合分け出来る:

  • 最大値がL未満の場合: 左側の子ノードの区間で光が当たる線分は自明に0本なので、右側の区間にのみ再帰を伸ばしていけばよい。
  • 最大値がL以上の場合: 左側の子ノードの区間で光が当たる線分は1本以上あるので、左側の区間再帰を伸ばす。その時に、右側を考えると、パラメータLに依存せず答えが一定になることがわかる(左側にL以上の値があれば、そちらがよりstrictな条件になるから)。そして、それはx - x_lで得られる(x_rではないことに注意)。

つまり、どちらの場合にせよ伸ばす再帰の方向はどちらか1つなので、この再帰はsegment tree上でO(logn)でクエリに答えることが出来る。実際のクエリでは、これが、O(logn)個の頂点で発生するので、O((logn)^2)で処理できる。

また、更新クエリに関しては点更新なので、事前計算で持っておくべき値xについて、計算し直す必要のあるノードがO(logn)個あるので、O((logn)^2)で処理できる。

実装(C++)

続きを読む