−目次
AtCoder Beginner Contest 178 D,E,F問題メモ
D - Redistribution
問題
- 各項が3以上の整数で、総和がちょうど S となる数列の個数を \mod{10^9+7} で求めよ
- 1 \le S \le 2000
解法
3という下駄が厄介なので、はじめに全要素に配る。
配った後に S がいくつ残るかは、数列の長さに依存する。
L=1~\dfrac{S}{3}(切り捨て)の範囲だけ調べればよい。
長さを L と決めると、全要素を3にするのに 3L かかる。 和を S にするには残り M=S-3L を L 個の項に配分すればよくて、 これは「L 個の選択肢から重複を許して M 個取る」と考えて重複組み合わせ {}_{L}H_{M} で求められる。
計算量は、O(S) かけて階乗を事前計算しておけば、O(S) 通りある L のそれぞれに対して答えが O(1) で求まるので、全体で O(S)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
def prepare(n, MOD): f = 1 factorials = [ 1 ] for m in range ( 1 , n + 1 ): f = f * m % MOD factorials.append(f) f = pow (f, MOD - 2 , MOD) finvs = [ 1 ] * (n + 1 ) finvs[n] = f for m in range (n, 1 , - 1 ): f = f * m % MOD finvs[m - 1 ] = f return factorials, finvs s = int ( input ()) MOD = 10 * * 9 + 7 facts, finvs = prepare(s, MOD) ans = 0 for l in range ( 1 , s / / 3 + 1 ): m = s - 3 * l ans = (ans + facts[l + m - 1 ] * finvs[m] * finvs[l - 1 ]) % MOD print (ans) |
E - Dist Max
問題
- N 個の点があり、i 番目の点は (x_i,y_i)
- 2点間マンハッタン距離の最大値を求めよ
- 2 \le N \le 2 \times 10^5
- -10^9 \le x_i,y_i \le 10^9
解法
シンプルな問題は、たいてい過去にどこかのコンテストで出題されていて、それに対する解説記事があったりする。
- arc047(B問題)
x'←x+y, \ \ y'←x-y と変換すると、マンハッタン距離 |x|+|y| はチェビシェフ距離 \max(|x'|,|y'|) になるので、
距離最大のものは \max(x'_{max}-x'_{min}, y'_{max}-y'_{min}) で求められる。
3次元以上にも拡張できる。
典型問題は知ってるか、知らなくてもググれば答えが見つかりそうと思えるか、によるところが大きい。
1 2 3 4 5 6 7 8 9 |
n = int ( input ()) xxx = [] yyy = [] for _ in range (n): cx, cy = map ( int , input ().split()) xxx.append(cx + cy) yyy.append(cx - cy) print ( max ( max (xxx) - min (xxx), max (yyy) - min (yyy))) |
F - Contrast
問題
- 長さ N の2つの整数列 A,B が与えられる
- それぞれは昇順にソートされている
- B_i を並べ替えて、どの i=1~N に対しても A_i \neq B_i としたい
- 可能か判定し、可能なら例を1つ示せ
- 1 \le N \le 2 \times 10^5
解法
解説読むと、ずらすだけ、と書かれていて「それでいけるのかー」ってなる。
以下、自分の取った解法。
破綻する条件を考えると「A,B にある個数の和」が多い数字から破綻しやすいので、そこから貪欲に確定。
判定
まず不可能なのを考えると、同じ数字が A にも B 一杯あるのは無理そう。
具体的には、一番ギリギリのケースは全ての i において A_i か B_i のどちらかに同じ数字が詰まっている状況で、ここからどちらかが1個でも増えると無理。
A 1 1 1 _ _ _ _ B _ _ _ 1 1 1 1
なので、整数 k の「A にある個数」「B にある個数」を C_k,D_k として、C_k+D_k \gt N なる k があったら不可能となる。
- 不可能となるのは分かったが、これさえ満たされていれば大丈夫、というのはHallの定理から言える
以下、構築は可能とする。
B をいったん空にして、確定できるところから割り当てていくことを考える。
どういう順で割り当てていくのがよいか。
こういう時、 「途中状態も、初期状態と同じような制約があり、割り当てた後も満たされた状態が保たれるならOK」 という考え方が使える場合がある。
B に既にいくつかの配置を割り当てたのがあったとして、
A 1 1 1 3 3 3 3 B 1 1 2 2 2 3 3 ↓ B _ _ _ 2 2 2 _ {未割り当て: 1 1 3 3}
改めて C_k,D_k を以下のように定義しなおすと、
- C_k: まだ相手のいない A_i の中での k の個数
- D_k: まだ割り当てていない k の個数
「C_k+D_k \gt 未割り当て数」となったら同様の理由で構築は不可能となる。
初期状態で C_k+D_k \gt N となる k が無ければ、配置さえ上手くやれば大丈夫 (途中まで割り当ててはじめて不可能とわかるケースはない)なので、 途中過程でこれを避けるように割り当てていく。
つまり C_k+D_k が大きい方から割り当てていくのがよさげ。
ちゃんと考えると、A_i=k である i に l を割り当てると、未割り当て数、C_k+D_k、C_l+D_l が1ずつ減る。
未割当数はどの数字でも共通なので、仮に C_m+D_m \le C_k+D_k なる m があったとして、
k に l を割り当てたために m で破綻したとすると、k 以外に割り当てたら k でも同様に破綻するはずである。
従って、「C_k+D_k \le 未割り当て数」を保とうと思えば、大きい k から優先的に減らして損は無い。
実装
以下を管理する。
- C_k+D_k の大きい方から k を取り出せる優先度キュー
- 各 k につき現在の C_k, D_k
- 各 k につき、未割り当ての A_i=k の中で最小の i
- C_k \gt 0, D_k=0 の k の集合
- A にだけ未割り当てが残り、B のどの要素を割り当てても問題ない
C_k+D_k の大きい2つを k=l,m として、A_i=m である i に l を割り当てていく。
この際、一部だけ割り当てて残ったものはまた後で処理しないといけない、ということがありそうなので、 常にその時の最大が取れるよう、優先度キューで管理する。
しかし、「C_k=0 だが D_k が大きいためにキューの先頭に来る k」またはその逆みたいなのがあると、
取り出しても割当元・割当先が無いので飛ばさなければならず、邪魔である。
そのため、優先度キューに入れるのは、C_k と D_k が共に正である
(互いに未割り当てが残り、上手くしないと破綻する可能性がある)k のみに限るとする。
優先度キューが最後の1個になったら、C_k \gt 0,D_k=0 である k があるはずなので、ストックしておいてそこから割り当てればよい。
優先度キューが空になったとき、未割り当てで残る数字で A と B に重複はないので、どこに何を割り当ててもよい。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 |
from collections import Counter from heapq import heapify, heappop, heappush from itertools import groupby n = int ( input ()) aaa = list ( map ( int , input ().split())) bbb = list ( map ( int , input ().split())) cnt_b = Counter(bbb) hq_both = [] free_a = [] idx_a = {} cnt_a = {} i = 0 for a, itr in groupby(aaa): cnt = len ( list (itr)) idx_a[a] = i cnt_a[a] = cnt if a in cnt_b: cnt_ab = cnt + cnt_b[a] if cnt_ab > n: print ( 'No' ) exit() hq_both.append(( - cnt_ab, a)) else : free_a.append(a) i + = cnt heapify(hq_both) ans = [ - 1 ] * n remaining = n while hq_both: cnt_ab, b = heappop(hq_both) cnt_ab = - cnt_ab assert cnt_ab = = cnt_a[b] + cnt_b[b] assert cnt_ab < = remaining cb = cnt_b[b] while cb: if hq_both: _, a = heappop(hq_both) else : a = free_a.pop() ca = cnt_a[a] fill_len = min (ca, cb) i = idx_a[a] ans[i:i + fill_len] = [b] * fill_len remaining - = fill_len idx_a[a] + = fill_len cnt_a[a] - = fill_len cnt_b[b] - = fill_len cb - = fill_len if cnt_a[a] > 0 : if a in cnt_b and cnt_b[a] > 0 : heappush(hq_both, ( - (cnt_a[a] + cnt_b[a]), a)) else : free_a.append(a) free_a.append(b) for b, cb in cnt_b.items(): if cb = = 0 : continue while cb: a = free_a.pop() ca = cnt_a[a] fill_len = min (ca, cb) i = idx_a[a] ans[i:i + fill_len] = [b] * fill_len idx_a[a] + = fill_len cnt_a[a] - = fill_len cb - = fill_len if cnt_a[a] > 0 : free_a.append(a) print ( 'Yes' ) print ( * ans) |
賢い解法
ソートされていることを利用して、ずらすだけ。
先述の通り「A と B に出てくる回数の合計が N より大きくなる数字が存在したら不可能」として、そうではないことを前提とする。
A を2回繰り返して、B をずらすだけにして考える。
前半サイクルと後半サイクルのそれぞれで、重ならない条件を考える。
前半←|→後半 A 1 1 1 2 2 2 3 3 3 1 1 1 2 2 2 3 3 3 B 1 2 2 2 2 2 2 3 3
繰り返す前の数列で以下を計算しておくと
- E_k: A の中で整数 k が終了する次のindex
- F_k: B の中で整数 k が開始するindex
- G_k: A と B で共通して出てくる k に対して、E_k-F_k
整数 k が前半サイクルと B で重ならないためには、B を G_k だけずらせばよい。
たとえば下図で'1'だけに着目すると、G_1=3 だけ右にずらすと、'1'に関しては重なりを回避できる。
E1 E2 E3 3 6 9 A 1 1 1 2 2 2 3 3 3 1 1 1 2 2 2 3 3 3 1 1 1 2 2 2 3 3 3 B 1 2 2 2 2 2 2 3 3 → 1 2 2 2 2 2 2 3 3 0 1 7 Aで'1'が終わったところにBの'1'の先頭を合わせるイメージ F1 F2 F3
すると、G_k の最大値を G_{max} とすると、B を G_{max} だけずらした結果は前半サイクルではどの数字も重ならない。
さて、次は後半サイクルだが、この時点で、後半サイクルでも重ならないことが言えてしまう。
なぜか。
B を G_k ずらすことで m が衝突してしまうような整数の組 (k,m) を仮定して、矛盾を導く。
また、A における m の前を l、B における m の後を n とする。
こんな感じになってる。
El Em A .......... l m m m m ........ B ........ m m m m m m m m n ... Fm Fn 衝突時 El+N v A .......... l m m m m ......... l m m m m ..... B --Gkずらす→ ........ m m m m m m m m n ... ~~~~~ ^ 衝突 Fn+Gk
m が端のために l,n が存在しない場合は、E_l=0 または F_n=N としておく。
衝突時の位置関係をindexで表すと、以下のようになっている。
- E_l+N \lt F_n+G_k = F_n+E_k-F_k
ここで、k と m の大小で場合分けする。
- k \lt m:
- 昇順に並んでいるので E_k \le E_l
- E_k+N \le E_l+N \lt F_n+E_k-F_k
- N \lt F_n - F_k
- しかし、1 \le F_n \le N, \ 0 \le F_k \le N-1 の2数の差が N を越えることは無い
- m \lt k:
- 昇順に並んでいるので F_n \le F_k
- E_l+N \lt F_n+E_k-F_k \le F_k+E_k-F_k
- N \lt E_k - E_l
- しかし、1 \le E_k \le N, \ 0 \le E_l \le N-1 の2数の差が N を越えることは無い
- k=m
- そもそもこれで衝突するなら全体で N+1 個以上あることになる
矛盾したので、衝突しない。よって、G_{max} だけずらして問題ない。
発想は単純でも、ちゃんとした証明はindexを式で表し、場合分けするという地道な作業が必要。
賢い解法2
B をひっくり返して、矛盾した箇所を適当に他の場所に持っていけばOK。
可能不可能判定は上記と同様。
ひっくり返して A にくっつけたとき、A_i=B_i となる箇所がなければそれでいいし、あってもそのような k は1通りに限られる。
また、k が重なって矛盾している数以上に、A_i \neq k,B_i \neq k となる i がある。(なかったら C_k+D_k \gt N になる)
そのような i で入れ替えれば矛盾を解消できる。
A 1 1 1 2 2 2 3 3 3 B 3 3 2 2 2 2 2 2 1 ^ ^ ^ 矛盾 k=2 ^ ^ ^ A≠k, B≠k ↓ A 1 1 1 2 2 2 3 3 3 B 2 2 2 3 3 1 2 2 2 ~~~<->~~~ ~ <-> ~