裏紙

ほぼ競プロ、たまに日記

CS Academy #14 - Subarrays Xor Sum

問題

CS Academy

問題概要

長さNの数列aが与えられる。長さがA以上B以下の部分列に対して、xorを取った値の和を10^9+7で割った余りを答えよ。

  •  1 \le A \le B \le N \le 10^5
  •  0 \le a_i \le 10^9

イデア

ビットごとに独立に考えていく。今、ビットを固定する(bとする)と、もとの数列aは01の数列として見なすことができるようになる((a[i]>>b)&1)。

こうなると、このビット列の中の部分列の中で、xorの値が1になる個数を数えるという問題に答えれば良いということになる。

さて、一般的な数列の部分和と同じように数列の部分に対してxorをとったものも同様に計算できる。iからjの部分和が欲しければ、prefix sum(pre)を用意しておいてそのpre_jpre_{i-1}に注目すれば良い。

次に、この部分列の長さに対する制約(A以上B以下)をどのように対処するかを考える。 個数を数えるにあたって、部分列の区間の右jを固定して考える。すると、この部分列の制約から区間の左のindexはj-B+1からj-A+1ということになる。つまり、pre_jの値に注目して、区間 [j-B, j-A]のpreに対して0と1の値が何個ずつあるかを保持しながら、jを増やしていくことで、この更新の処理を尺取的にできるので1つのビットに対してO(N)で計算ができ、十分に間に合う。

実装(C++)

続きを読む

CS Academy #20 - Palindromic Concatenation

問題

Archive

問題概要

文字列がn個与えられる。i番目の文字列をs_iと表す時、s_is_jの連結(この順番に並べて結合した文字列)が回文になるような組(i,j)(i \neq j)の個数を求めよ。

  • 1 \le n \le 10^5
  • 1 \le \sum_{i=1}^{n} | s_i | \le 10^5
  • s_iは英小文字のみで構成される
  • (i,j)(j,i)は区別される

イデア

s_i + s_jが回文になる時、どのような状況になれば条件を満たすかを考えてみる。この条件は文字列の長さに依存するので、以下のように3つに条件を分けて考える:

  1.  | s_i | = | s_j |
  2.  | s_i | \lt | s_j |
  3.  | s_i | \gt | s_j |

1のときに満たすべき条件はシンプルで、s_ireverseしたらs_jに一致することである。

次に、2のときに満たすべき条件を考えてみる。s_iの方が短いので、回文の中心はs_jの途中の位置に来ることになる。よって、それぞれの文字列の長さをI , Jとおけば

  • s_ireverseした文字列とs_jの末尾からI文字が一致
  • s_jの先頭からJ-I文字が回文になっている

という2つの条件を同時に満たす必要があることが分かる。

3についても対称に考えれば、

  • s_jreverseした文字列とs_iの先頭からJ文字が一致
  • s_iの末尾からI-J文字が回文になっている

という2つの条件を同時に満たす必要があることが分かる。

nの制約から、それぞれのペアをとってきて比較するということは間に合わなさそうなので、別の方針を考える必要がある。 そこで、ハッシュ値を利用して各s_iとペアを作ることが出来る文字列の個数を探す。

条件1に関しては、各文字列をreverseしたときのハッシュ値をカウントしておけば簡単に計算できる。以下では2,3について考える。

まず、n個の文字列は長い方から順に処理していくことにする。そして、mapにハッシュ値をカウントしていく。そのときに、先頭からx文字が回文になっていれば残りの末尾の文字のハッシュ値を+1し、末尾からx文字が回文になっていれば残りの先頭の文字のハッシュ値を+1とする。

この、「回文になっているかどうか」という判定は、各文字列に対して元の文字列と、reverseした後の文字列でそれぞれローリングハッシュを構築し、そのハッシュ値の比較をすることでO(1)で判定することが可能になる。

各文字列s_iに対して、ペアを作ることが出来る文字列の個数はこのs_ireverseしたもののハッシュ値のカウントに一致する。

実装(C++)

続きを読む

CF 798 D - Mike and distribution

問題

Problem - D - Codeforces

問題概要

長さnの数列abが与えられる。

いま、数列のindexをk個選ぶことが出来る。選んだk個のindexをp_1, p_2, \ldots , p_kとするとき、次の条件を満たすようにk個のindexを選べ:

  •  k \le \lfloor \frac{n}{2} \rfloor + 1
  • \displaystyle 2 \sum_{i=1}^{k} a_{p_i} \gt \sum_{i=1}^{n} a_i
  • \displaystyle 2 \sum_{i=1}^{k} b_{p_i} \gt \sum_{i=1}^{n} b_i

このような条件を満たすP = \{ p_1, p_2, \ldots , p_k \}を答えよ。

  •  1 \le n \le 10^5
  •  1 \le a_i \le 10^9
  •  1 \le b_i \le 10^9

イデア

まず、kは多いほうが得だから限界まで増やすことにする。

そして、このabのペアを、残っている中で、aが最大bが最小aが最大→…と交互にルールを適用していって並べていく。例を示すと、n=5の時は次のようになる。

f:id:imulan:20170422171543p:plain

このように並べることで、局所的に大小関係が発生するので、奇数番目に現れるものを選んでいけば必ず全体の和の半分より大きい値を得られる。nが偶数の時は、最後のindexを1つ前に詰めておけばよい。

乱択でも通ってしまうっぽい…(submission)。

実装(C++)

続きを読む