Processing math: 91%

Toyota Programming Contest 2024#4(AtCoder Beginner Contest 348)F,G問題メモ

F - Oddly Similar

問題

  • N×M の行列 A が与えられる
  • Ai 行目と j 行目は、「Ai,k=Aj,k となる k1kM)」の個数が奇数の時、「似ている」という
  • 似ている行のペア (i,j)(ただし i<j)の個数を求めよ
  • 1N,M2000
  • 1Ai,j999

解法

こんなん、愚直に見ていくしか方法が無さそうだが、愚直に見ていくと O(N2M) かかる。
(C++などではそれで間に合っちゃうらしいが)

一致する“個数”でなく“偶奇”だけがわかればよいことから、bitsetのxorを用いて定数倍を軽くする方法が考えられる。

「2 列目の値が 3 であるような行は、i=0,3,4 行目」といったように、 (j,Ai,j) の組毎に、合致する i の集合を持たせたデータを考える。

ある列 j で同じ集合に属する行は、互いに j 列目で値が一致していることになる。

ある行 i について、自身と似ている行を探すには、

  j  0  1  2  3  4
i
0    1  2  3  4  5
1    1  2  4  4  5
2    2  1  4  3  5
3    5  4  3  2  1
4    2  1  3  4  5

i=4 について
 j,A   iの集合(自身以外)
        43210
(0,2)   00100
(1,1)   00100
(2,3)   01001
(3,4)   00011
(4,5)   00111
------------- xor
        01101

このように、着目中の行の各 (j,Ai,j) に対する i の集合をxorすると、最後に“1”が立っているものが「似ている」列となる。

実際は、i<j のペアのみ探すので、i が小さい方から順に、求解→i の集合への登録、を処理していくと重複を除ける。
(自身を j として、自身より小さい i に限定したペアを求める)

A の制約範囲が狭めなので、(j,A) は高々 2×106 通りしか取り得ず、 それぞれに N bitのbitsetを持たせても、4×109646.25×107 でメモリ的には大丈夫。

計算量も、N 個の行に付き、M 回、N64 サイズのxor演算を行うため、 総合して N2M641.25×108 となり、遅い言語では厳しめだが、なんとか間に合う。

Python3


PyPyってたまに、わずかな違いで実行速度に差が生じることがある。
上手にコンパイルしてくれる書き方があるのだろうけど、根本的な要因がわからない。

G - Max (Sum - Max)

問題

  • 長さ N の2つの整数列 A,B が与えられる
  • k=1,2,...,N について、以下に答えよ
    • I={1,2,...,N} から k 個選ぶ
    • 「選んだindexの Ai の和」-「選んだindexの Bi の最大値」の最大値を求めよ
  • 1N2×105

解法

利用するテクニックも難しめだし、この問題がそれを利用できることに気付くのもなかなか難しい。
(類題を見たことあれば「あ、」と気付くものかもしれない)

最小費用流で解けるかと思ったが、引く数が「選んだindexの Bi の最小値」ならいけるが、最大値なのでダメだった。

まず、(Ai,Bi)Bi を基準にソートする。
こうすると、引く数は「選んだ中で最も右の Bi を使う」と確定するので考えやすくなる。

分割統治

以下のDPを定義して分割統治をする。

  • dp[l,r,k]= 区間 [l,r) から k 個選んだときの最大値

初期値として、区間の長さが1の時、dp[l,l+1,1]=AlBl である。

ここから、区間を統合して倍の長さの区間の答えを求める、ということを繰り返して、dp[0,N,k] の答えを求めたい。
(2の冪からはみ出す分は、Ai=Bi= などで埋めておけばよい)

i   0  1  2  3  4  5  6  7
A   3 -1  4  1 -5  9  2 -∞
B  -5 -2 -2  0  1  4  8  ∞

dp  8  1  6  1 -6  5 -6 -∞  ←長さ1の区間 dp[i,i+1,1] = Ai-Bi
   |----||----||----||----|
   |----------||----------|  区間を倍々にして dp[l,r,*] を求めていく
   |----------------------|

[l,m)[m,r) を統合して dp[l,r,k] を求めるにあたり、[l,m) の方から s 個、取ることにすると、

  • s<k のとき
    • [l,m) の方からは、Bi を気にする必要は無いので、Ai の大きい方から s 個、貪欲にとった総和としてよい
    • [m,r) の方からは、dp[m,r,ks] が達成できる
    • この両区間の和が達成できる
  • s=k のとき
    • 右からは取らないので、dp[l,m,k] となる

s=0,1,...,k を試して、この中の最大値が、dp[l,r,k] となる。

当然、これを k ごとにやっていたら、k0,1,...,rl に付いて求めるため、O((rl)2) かかる。
分割統治全体では、rlN 以上になるまで、22N/2 回、42N/4 回、、、と繰り返されるため、 O(N2) かかることになる。

最大値畳み込み

dp[l,r,] を求めるのは、最大値の畳み込みに相当する。

要は、2つの数列 X,Y で、Xs を左区間(区間内の Ai の大きい方から s=0,1,... 個取ったもの)、Yt を右区間(dp[m,r,t])としたときに、

  • Zk=max

となる数列 Z を求められれば、dp[l,r,*] が一気に求まる。

この畳み込みは、普通は O(|X||Y|) かかるが、 X,Y のいずれかが上に凸である場合、O(|X|+|Y|\log|X|) などで求める方法がある。

今回の場合、左区間の X_s が「大きい方からの累積和」ということで上に凸であるため、この高速化手法を適用できる。

これを使ってdpを倍々に統合していくと、全体で O(N \log N) で求まる。

Python3

programming_algorithm/contest_history/atcoder/2024/0406_abc348.txt · 最終更新: 2024/04/19 by ikatakos
CC Attribution 4.0 International
Driven by DokuWiki Recent changes RSS feed Valid CSS Valid XHTML 1.0