Processing math: 39%

AtCoder Regular Contest 160 A,C,D問題メモ

A - Reverse and Count

問題

  • (1,2,,N) の順列 A が与えられる
  • A の区間 [L,R] の要素を反転させた順列を f(L,R) とする
  • (L,R)1LRN から選ぶ方法は N(N+1)/2 通りある
  • すべての (L,R) に対する順列 f(L,R) を列挙し、辞書順に並べ替えたとき、K番目の順列を求めよ
  • 1N7000

解法

indexを 0LRN1 になおして考える。

LL=0 から順番に試す。

辞書順なので兎にも角にも先頭に“1”を持ってくるのが一番小さくて、 その方法は(最初から A0 が“1”だった場合を除き)L=0R=(Ai=1i) とする1通りである。

 4 3 1 6 2 5
 L   R
[1 3 4]6 2 5

また、“2”にするのも(最初から A0 が“2”だった場合を除き)1通りで、これが2番目に小さい順列となる。

 4 3 1 6 2 5
 L       R
[2 6 1 3 4]5

同様に考えると、先頭が A0 以外になる順列はそれぞれ1通りしか作れず、順位が確定する。

4 3 1 6 2 5    → f(L,R)は21通り

辞書順    
     1    (先頭が1) 確定
     2    (先頭が2) 確定
     3    (先頭が3) 確定
     4    (先頭が4)
     5    (先頭が4)
     :
    19    (先頭が4)
    20    (先頭が5) 確定
    21    (先頭が6) 確定

先頭が“4”になるのは、L1 か、または L=R=0 であり実質 A から変わらない順列のいずれかとなる。

ともかく、順位 1~3, 20~21 が確定し、4~19 が未確定となった。

これを L=1,2,3,... で再帰的におこなえば、徐々に未確定順位が絞られていく。

  • L=1 の時、A1=3
    • i=1 より右にある Ai で、3より小さいのは2個、大きいのは2個
    • → 未確定順位 4~19 のうち、先頭2個 4~5 と、末尾2個 18~19 が確定
    • → 残る未確定順位は 6~17 となり、L=2 に進む

処理の途中で K 番目が確定すればよし、確定しなければ次に進むことを繰り返す。

L=N1 まで処理すると、最終的に未確定順位が N 個分残る。
これは L=R とした時のものであり、K が未確定順位内にある場合は A のままが答えとなる。

Python3

C - Power Up

問題

  • 正整数の N 要素の多重集合 A={A1,...,AN} が与えられる
  • 以下の操作を0回以上、好きなだけおこなえる
    • A に2個以上含まれる要素を選ぶ(x とする)
    • A から x を2個削除し、x+1 を1個追加する
  • 操作後の A としてあり得る多重集合の種類数を 998244353 で割ったあまりで求めよ
  • 1N2×105
  • 1Ai2×105

解法

DP。割と素直な考え方で解けるが、計算量をちゃんと評価する部分に若干の難しさがある。
(とはいえ、「何となく大丈夫そう」で通ってしまうことも多いけど)

x 2個が x+1 1個になることを、「x+1 に1個繰り上がる」と表現することにする。

以下のDPで解ける。

  • DP[i][j]=ij 個繰り上がる、i1 以下の要素だけに操作を行った場合の多重集合の種類数

例えば初期状態で“1”が7個あったら、03 個まで“2”に繰り上げることができるので、

         0  1  2  3
DP[2] = [1, 1, 1, 1]

となる。次、加えて“2”が例えば5個あったら、DP[2]j=03 につき、DP[3]0j+52 に遷移できる。

Aの初期状態に"2"が5個:

DP[2][0]  →  DP[3][0~2] のそれぞれに加算
DP[2][1]  →  DP[3][0~3] のそれぞれに加算
DP[2][2]  →  DP[3][0~3] のそれぞれに加算
DP[2][3]  →  DP[3][0~4] のそれぞれに加算

結果     0  1  2  3  4
DP[3] = [4, 4, 4, 3, 1]

加算部分は累積和で高速化できる。

これを、imax を超えた上で、|DP[i]|=1 になるまで繰り返せば、DP[i][0] が答え。

計算量見積もり

  • i の範囲は \min(A)~(\max(A)+\log{N})
    • \log{N} は、\max(A) 以降もまだしばらく繰り上がれる場合があるから
  • j は繰り上がりの個数なので最大 \frac{N}{2}

なので単純に掛け合わせると O(N(A_{\max}+\log{N})) となり、TLEとなる。
しかし、それはあり得ない (i,j) ペアまで含めてしまっているからで、有効なものだけを考えると、状態はぐっと減る。

ざっくり見積もりとして、以下が利用できる。

  • A の初期状態に i がない場合が続くと、DP[i] の長さは i が1増えるたびに半分ずつになっていく。

初期状態に i があるとそこで長さが追加されるが、その場合も以下のように考えると、

細かな誤差とかは無視して、
「繰り上がってきた分」と「Aの初期状態にあった追加分」の
DP上での行く末を分けて考える
(本来は±1の端数のため、明確に分けられるものでもないが、ざっくり)

         0 1 2 3 4 5
DP[2]  [ o o o o o o ]
         ↓   ,-----'
DP[3]  [ o o o|o o o o o o ]
         ↓ ,-'   ,-------'
DP[4]  [ o o|o o o|o o o ]
         ~~~                2から追加された
             ~~~~~          3から追加された
                   ~~~~~    4から追加された

結局、それぞれの区間の長さは約半分ずつになっていくのは変わらない。
ある長さ n の区間が半分ずつになっていって、なくなるまでの長さの合計は約 2n である。

DPに追加される長さの総計が O(N) なので、それが一括であろうと分割されていようと、 全ての区間が無くなるまでの DP[i] の長さの合計は O(N) で収まることになる。

ただ、N=2,A=\{1,200000\} のようなケースなど、繰り上がりが発生しなくても上限までの DP[i][0] は遷移させ続けないといけないので、 計算量は O(N + A_{\max}) となる。

Python3

D - Mahjong

問題

  • 正整数 N,M,K が与えられる
  • 以下の条件を満たす数列 A=(A_1,...,A_N) の個数を 998244353 で割ったあまりで求めよ
    • 長さは N、総和は M
    • 以下の操作のどちらかを選んで行うことを繰り返すことで、全ての項を 0 にできる
      • 任意の項 A_i から K 引く
      • 任意の連続する KA_i,...,A_i+K-1 から 1 ずつ引く
  • 1 \le K \le N \le 2000
  • 1 \le M \le 10^{18}

解法

制約付き重複組み合わせ。計算量を減らす発想も必要。

大まかな方針イメージ

まず、どちらの操作にしても総和が K ずつ少なくなるので、M \mod{K} \equiv 0 以外の場合は不可能。
可能な場合、d=\frac{M}{K} として、d 回、いずれかの操作をすることになる。

操作を「縦」「横」で表現することにする。

縦操作のパターンは、i=1~N のそれぞれに対して、そこに何回操作をおこなえるかで決まる。
横操作のパターンは、i=1~N-K+1 のそれぞれに対して、そこを左端とする横操作を何回おこなえるかで決まる。

横操作の回数を y、縦操作を d-y として、 それぞれの重複組み合わせ {}_NH_{d-y} \times {}_{N-K+1}H_{y} とかで計算できそう。。。

重複除外

しかし、これでは以下の2パターンが同じになり、重複して数えてしまう。

  • ある連続する K 項に縦操作をそれぞれ1回ずつおこなえるもの
  • 同じ i に横操作を K 回おこなえるもの

重複を防ぐため、「横操作は、1箇所に対して K-1 回までしかおこなえない」とする。 (「縦操作は連続する K 箇所に行えない」としてもよいが、まぁ、横操作の方が考えやすそう)

以下、1箇所の上限が K-1 までと決まっている重複組み合わせを、{}_nH'_{r} で表す。

計算量を削減する

横操作の回数を固定し、その場合の横操作と縦操作のパターン数の積を計算したい。
横操作の回数の上限は、N-K+1 箇所に上限 K-1 を配置するため、(N-K+1)(K-1) となる。

  • 横操作の回数を y と固定して、
  • \displaystyle \sum_{y=0}^{(N-K+1)(K-1)} {}_{N-K+1}H'_{y} \times {}_{N}H_{d-y}

数え上げる式は立てられたが、これでは計算量が多すぎる。個数上限のある重複組み合わせの求め方と、y に対するループの部分がネック。

個数上限付き重複組み合わせの求め方

個数上限付き重複組み合わせは、例えば以下の2通りの求め方がある。

  • DP
  • 包除原理

DPなら、{}_nH'_r は以下のサイトのように O((nの範囲) \times (rの範囲)) で出せるが、

今回の場合、n の上限は N+K-1r は横操作の回数上限 (N-K+1)(K-1) なので、O(N^3) となりDPでは無理そう。

包除原理は、個数上限が一定であるときに特に使いやすくて、 n 個のうちいくつが(明示的に)制約違反しているかを求めることで、1回当たり O(N) で求められる。

  • \displaystyle {}_{N-K+1}H'_{y} = \sum_{t=0}^{N-K+1} (-1)^t \times {}_{N-K+1}C_{t} \times {}_{N-K+1}H_{y-tK}
    • あらかじめ制約違反すると決める t 個の選び方 × それ以外の配り方
横と縦をまとめる工夫

包除原理で、上限付き重複組み合わせを O(N) で計算できるとはいっても、

  • 横方向の回数ごとに場合分けすると、O(N^2) 回繰り返す必要がある
  • {}_nC_{r} を求める際、n,r が非常に大きいことがあり、事前計算ができないので、1回ずつ計算する必要がある
    • 今回の制約では n-rO(N) に収まるので、\frac{n(n-1)...(r+1)}{(n-r)!}O(N) で計算する

ために、全体でまだ O(N^4) かかってしまう。

実は、縦操作と横操作の回数は、場合分けする必要が無い。
回数の和が d と決まっている2つの重複組み合わせなので、 「縦操作 N と横操作 N-K+1、計 2N-K+1 から d 個を重複を許して選ぶ」とすると、まとめて一括で考えられる。

  • \displaystyle \sum_{y} {}_{N-K+1}H_{y} \times {}_{N}H_{d-y} = {}_{2N-K+1}H_{d}

この場合でも、「その中で横操作に対応する N-K+1 個だけは、K 個を超えて選べない」という個数上限は、 包除原理で問題なく計算できる。

これで、全体で O(N^2) に収まる。

Python3

programming_algorithm/contest_history/atcoder/2023/0514_arc160.txt · 最終更新: 2023/05/16 by ikatakos
CC Attribution 4.0 International
Driven by DokuWiki Recent changes RSS feed Valid CSS Valid XHTML 1.0