−目次
AtCoder Beginner Contest 178 D,E,F問題メモ
D - Redistribution
問題
- 各項が3以上の整数で、総和がちょうど S となる数列の個数を mod109+7 で求めよ
- 1≤S≤2000
解法
3という下駄が厄介なので、はじめに全要素に配る。
配った後に S がいくつ残るかは、数列の長さに依存する。
L=1~S3(切り捨て)の範囲だけ調べればよい。
長さを L と決めると、全要素を3にするのに 3L かかる。 和を S にするには残り M=S−3L を L 個の項に配分すればよくて、 これは「L 個の選択肢から重複を許して M 個取る」と考えて重複組み合わせ LHM で求められる。
計算量は、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 番目の点は (xi,yi)
- 2点間マンハッタン距離の最大値を求めよ
- 2≤N≤2×105
- −109≤xi,yi≤109
解法
シンプルな問題は、たいてい過去にどこかのコンテストで出題されていて、それに対する解説記事があったりする。
- 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 が与えられる
- それぞれは昇順にソートされている
- Bi を並べ替えて、どの i=1~N に対しても Ai≠Bi としたい
- 可能か判定し、可能なら例を1つ示せ
- 1≤N≤2×105
解法
解説読むと、ずらすだけ、と書かれていて「それでいけるのかー」ってなる。
以下、自分の取った解法。
破綻する条件を考えると「A,B にある個数の和」が多い数字から破綻しやすいので、そこから貪欲に確定。
判定
まず不可能なのを考えると、同じ数字が A にも B 一杯あるのは無理そう。
具体的には、一番ギリギリのケースは全ての i において Ai か Bi のどちらかに同じ数字が詰まっている状況で、ここからどちらかが1個でも増えると無理。
A 1 1 1 _ _ _ _ B _ _ _ 1 1 1 1
なので、整数 k の「A にある個数」「B にある個数」を Ck,Dk として、Ck+Dk>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}
改めて Ck,Dk を以下のように定義しなおすと、
- Ck: まだ相手のいない Ai の中での k の個数
- Dk: まだ割り当てていない k の個数
「Ck+Dk> 未割り当て数」となったら同様の理由で構築は不可能となる。
初期状態で Ck+Dk>N となる k が無ければ、配置さえ上手くやれば大丈夫 (途中まで割り当ててはじめて不可能とわかるケースはない)なので、 途中過程でこれを避けるように割り当てていく。
つまり Ck+Dk が大きい方から割り当てていくのがよさげ。
ちゃんと考えると、Ai=k である i に l を割り当てると、未割り当て数、Ck+Dk、Cl+Dl が1ずつ減る。
未割当数はどの数字でも共通なので、仮に Cm+Dm≤Ck+Dk なる m があったとして、
k に l を割り当てたために m で破綻したとすると、k 以外に割り当てたら k でも同様に破綻するはずである。
従って、「Ck+Dk≤ 未割り当て数」を保とうと思えば、大きい k から優先的に減らして損は無い。
実装
以下を管理する。
- Ck+Dk の大きい方から k を取り出せる優先度キュー
- 各 k につき現在の Ck,Dk
- 各 k につき、未割り当ての Ai=k の中で最小の i
- Ck>0,Dk=0 の k の集合
- A にだけ未割り当てが残り、B のどの要素を割り当てても問題ない
Ck+Dk の大きい2つを k=l,m として、Ai=m である i に l を割り当てていく。
この際、一部だけ割り当てて残ったものはまた後で処理しないといけない、ということがありそうなので、 常にその時の最大が取れるよう、優先度キューで管理する。
しかし、「Ck=0 だが Dk が大きいためにキューの先頭に来る k」またはその逆みたいなのがあると、
取り出しても割当元・割当先が無いので飛ばさなければならず、邪魔である。
そのため、優先度キューに入れるのは、Ck と Dk が共に正である
(互いに未割り当てが残り、上手くしないと破綻する可能性がある)k のみに限るとする。
優先度キューが最後の1個になったら、Ck>0,Dk=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
繰り返す前の数列で以下を計算しておくと
- Ek: A の中で整数 k が終了する次のindex
- Fk: B の中で整数 k が開始するindex
- Gk: A と B で共通して出てくる k に対して、Ek−Fk
整数 k が前半サイクルと B で重ならないためには、B を Gk だけずらせばよい。
たとえば下図で'1'だけに着目すると、G1=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
すると、Gk の最大値を Gmax とすると、B を Gmax だけずらした結果は前半サイクルではどの数字も重ならない。
さて、次は後半サイクルだが、この時点で、後半サイクルでも重ならないことが言えてしまう。
なぜか。
B を Gk ずらすことで 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 が存在しない場合は、El=0 または Fn=N としておく。
衝突時の位置関係をindexで表すと、以下のようになっている。
- El+N<Fn+Gk=Fn+Ek−Fk
ここで、k と m の大小で場合分けする。
- k<m:
- 昇順に並んでいるので Ek≤El
- Ek+N≤El+N<Fn+Ek−Fk
- N<Fn−Fk
- しかし、1≤Fn≤N, 0≤Fk≤N−1 の2数の差が N を越えることは無い
- m<k:
- 昇順に並んでいるので Fn≤Fk
- El+N<Fn+Ek−Fk≤Fk+Ek−Fk
- N<Ek−El
- しかし、1≤Ek≤N, 0≤El≤N−1 の2数の差が N を越えることは無い
- k=m
- そもそもこれで衝突するなら全体で N+1 個以上あることになる
矛盾したので、衝突しない。よって、Gmax だけずらして問題ない。
発想は単純でも、ちゃんとした証明はindexを式で表し、場合分けするという地道な作業が必要。
賢い解法2
B をひっくり返して、矛盾した箇所を適当に他の場所に持っていけばOK。
可能不可能判定は上記と同様。
ひっくり返して A にくっつけたとき、Ai=Bi となる箇所がなければそれでいいし、あってもそのような k は1通りに限られる。
また、k が重なって矛盾している数以上に、Ai≠k,Bi≠k となる i がある。(なかったら Ck+Dk>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 ~~~<->~~~ ~ <-> ~