AtCoder Beginner Contest 473 D,E,F,G問題メモ

D - Coefficient Stair

問題文

  • 非負整数からなる長さ $N$ の列 $A=(A _ 1,A _ 2,\ldots,A _ N)$ であって、$\displaystyle\sum _ {i=1} ^ Ni\times A _ i=K$ を満たすものを辞書順で小さいほうからすべて出力してください。
  • ここで、条件を満たす数列が $3\times10 ^ 5$ 個以下であるような入力のみが与えられます。

制約

  • $1\le N\le 10$
  • $1\le K\le 2\times10 ^ 5$
  • 条件を満たす数列は $3\times10 ^ 5$ 個以下
  • 入力はすべて整数

解法

個人的な好みではあるんだが、 問題を解くのって「入力に対し未知の出力結果を知りたいからプログラムを作る」感覚があるので、 この問題のように「その未知の結果(条件を満たす数列の個数)を知らないと設定できない入力制約」って、循環的でどことなく奇妙に感じてしまう。

$K$ の範囲は大きいが、$K$ が増えると爆発的に条件を満たす数列の個数が増えるので、実質的な上限はさほど大きくないことが想定される。 例えば $N=10$ の場合、実質 $K \le 64$ という制約となる。(この時 $296320$ 個)

このことに気付けば、比較的愚直な解法が可能であると推測できる。以下の再帰関数でDFSすれば求められる。

  • $f(tmp, i, j):=$ 現在 $tmp=(A_1,...,A_{i-1})$ と決め、次に $A_i$ を決める段階で、残りの和を $j$ とするような方法を列挙

$A_i$ を $t=0~\left \lfloor \dfrac{j}{i} \right \rfloor$ まで試しながら $f((tmp...,t),i+1,j-it)$ に進めていくとよい。

Python3

E - K-Divisible Subarrays

問題文

  • 非負整数からなる長さ $N$ の列 $A=(A _ 1,A _ 2,\ldots,A _ N)$ が与えられます。
  • 非負整数列からなる長さ $1$ 以上の列 $S=(S _ 1,S _ 2,\ldots,S _ k)$ のスコアを、列 $S _ i\ (1\le i\le k)$ のうち要素の総和が $K$ で割り切れるものの個数として定めます。
  • $A$ を $1$ つ以上の連続する部分列に分割して得られる非負整数列の列に対するスコアの最大値を求めてください。

制約

  • $1\le N\le2\times10 ^ 5$
  • $1\le K\le10 ^ 9$
  • $0\le A _ i\lt K$
  • 入力はすべて整数

解法

「$B=$ 累積和 $\bmod{K}$」を計算しておく。

K=10
i     1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17
A     3  1  4  1  5  9  2  6  5  3  5  8  9  7  9  3  2
B  0  3  4  8  9  4  3  5  1  6  9  4  2  1  8  7  0  2

すると、$A$ から取れる部分列のうち、総和が $K$ で割り切れる中で「最も右端が左」なのは $(4,1,5)$ とわかる。
$B$ の中で“2個目の登場が最も早い値”が $i=2,5$ の $4$ なので。

この時 $(4,1,5)$ は必ず使うとして損しない。 仮に最適解の1つにおいて、これより右端が右にある部分列が “最初にスコアに寄与する部分列” なら、 スコアを減らさず $(4,1,5)$ を使うように変更できるので。

同様に、その次にスコアに寄与するのは $(4,1,5)$ 以降で $B$ の中で同じ値が2回登場する最も早い区間としてよい。
上例だと、$i=5$ から再スタートして $i=5,11$ の $4$ が最も早いので、$(9,2,6,5,3,5)$ となる。

更にその次は $i=12,17$ の $2$ なので $(9,7,9,3,2)$、、、と貪欲に決めていける。

Python3

F - A/AB Insertion

問題文

  • 文字列 $T$ が「良い文字列」であるとは、$T$ が以下の操作で得られる文字列であることを指します。
    • 空文字列から始め、以下の $2$ つの操作を好きな順序で $0$ 回以上何回でも行う。
      • 文字列の(先頭・末尾を含めた)任意の箇所を選択し、そこに A を挿入する。
      • 文字列の(先頭・末尾を含めた)任意の箇所を選択し、そこに AB を挿入する。
  • 長さ $N$ の A, B からなる文字列 $S$ が与えられます。
  • 以下のクエリを合計 $Q$ 個処理してください。
    • 1 i c :
      • $S$ の $i$ 文字目を $c$ に変更する。
    • 2 l r :
      • 現在の文字列 $S$ の $l$ 文字目から $r$ 文字目までを抜き出した文字列が「良い文字列」であれば Yes 、そうでなければ No と出力する。

制約

  • $N$ は $1 \le N \le 5 \times 10^5$ を満たす整数
  • $S$ は A, B からなる長さ $N$ の文字列
  • $Q$ は $1 \le Q \le 2 \times 10^5$ を満たす整数
  • 与えられるクエリはタイプ $1,2$ のいずれかである
  • タイプ $1$ のクエリは以下の制約を満たす
    • $i$ は $1 \le i \le N$ を満たす整数
    • $c$ は A, B のいずれか
  • タイプ $2$ のクエリは以下の制約を満たす
    • $l,r$ は $1 \le l \le r \le N$ を満たす整数

解法

E問題ほどあからさまではないが、これも言い換えると同じく累積和が関わってくる。

良い文字列であるとは、以下と同義である。

  • 'A'を $+1$、'B'を $-1$ に置換して、先頭から累積和を取った時に、一度も負値にならない

'B'は挿入するとき、その前に必ず'A'を伴わないといけない。 つまり'B'の前にはその個数以上の'A'がないといけない。
逆にそれが満たされるなら、操作を逆に考えて「'A' または 'AB' を取り出していく」と考えたとき、 'B'がなくなるまで'AB'の並びは常に存在するので、構築可能である。

よって、区間加算・区間最小値取得の遅延セグメント木などで可能である。

累積和を載せておき、クエリ1でA→Bに変わったならそれ以降に全て $-2$、 B→Aなら $+2$ を加算すれば累積和が正しい状態に保たれる。

クエリ2では累積和の「$l$ の値」と「$[l,r]$ の最小値」が等しいか調べればよい。

Python3

G - Wipeout

問題文

  • 表に整数 $1,2,\dots,N$ が書かれたカードが $1$ 枚ずつ、計 $N$ 枚のカードが裏返しで一列に並べられています。
    • これらのカードの並び順は $N!$ 通りの中から一様ランダムに定められます。
  • あなたは表に整数 $1,2,\dots,N$ が書かれたカードが $1$ 枚ずつあること、カードが一様ランダムに並べられたことを知っていますが、カードの表に書かれている整数に関する情報はこれ以上持っていません。
  • あなたは以下のゲームを行います。
    • 最初、変数 $x=1$ とする。
    • $x \le N$ である限り、以下の操作を繰り返す。 $1$ 回の操作は以下の $3$ つの手順からなる。
      • カードを $1$ 枚指定して、それを表に向ける。
      • カードに書かれた整数が $x$ ならそのカードを食べ、 $x$ に $1$ 加算する。
      • そうでないなら、そのカードを裏に向ける。あなたはそのカードに書かれた整数を永久に記憶しておくことができる。
  • あなたは常に全てのカードを食べ終わるまでの操作の総数の期待値が最小となるように行動します。
  • この場合、操作の総数が $K$ 回となる確率はいくらでしょうか? $\bmod{998244353}$ で求めてください。

制約

  • 入力は全て整数
  • $1 \le N \le 5 \times 10^5$
  • $N \le K \le 10^9$

解法

先頭から順にめくるものと考えてよい。
カードの並び順を $P$、めくる順を $Q$ のように2種類の $1~N$ の順列 $P,Q$ を定めたとして、 それぞれは一様ランダムであり、結局その2つを合成した順列を先頭からめくるのと同じである。

まず、$1$ が出ないと進まない。
それまでに出たカードは、$x$ がその値になったときにそれぞれもう一度だけめくることになる。それまではめくる必要は無い。

$1$ が出たら、それまでに $2,3,...$ が出ていたらそれをめくり、 再び $1$ の次からめくっていないカードをめくっていく。 次に出てくれないと進まない値は「まだ出てない最小値」となる。

これを繰り返し最後までめくると、経験した「出てくれないと進まない値(★)」の個数を $L$ として、 $L$ 枚を1度だけめくり、$N-L$ 枚は都合2回ずつめくることになるので、操作回数は $2N-L$ となる。

$L$ の値は $L=2N-K$ より入力から決まる。これが $1 \le L \le N$ の範囲にならない場合は確率 $0$。

で、$L$ に対する答えを求めていく。
最後に $N!$ で割るとして、★の個数が $L$ 個となるような順列の個数が分かればいい。 これは順列を $1,2,...,N$ の順に挿入して構築していく中で、 「末尾に追加した回数が $L$ 回」であるようなものの個数と言い換えられる。

よって、$O(N^2)$ でTLEとはなるが、以下のようなDPで求めることができる。

  • $\mathrm{DP}[i,j]:=i$ までを挿入し、末尾に挿入した回数が $j$ 回であるような、$1~i$ の順列の個数

$\mathrm{DP}[0,0]=1$ より始め、$\mathrm{DP}[N,L]$ が求めたいものとなる。

このDPは、$i$ を「末尾に挿入するなら1通り」「末尾以外に挿入するなら $i-1$ 通り」の挿入箇所があるので、

i\j     0   1   2   3   4
0       1
     x0↓↘x1
1       0   1
     x1↓↘↓↘x1
2       0   1   1
     x2↓↘↓↘↓↘x1
3       0   2   3   1
     x3↓↘↓↘↓↘↓↘x1
4       0   6  11   6   1

このように、↓は $i-1$ 倍、↘は $1$ 倍で $\mathrm{DP}[i-1]$ の結果を遷移させればよい。

これは形式的冪級数に置き換えると $(i-1 + x)$ をかけることに相当する。(A130534

よって、全体では $x(x+1)(x+2)...(x+N-1)$ の結果の $x^L$ の係数を求めればよいということになる。

項数の小さい方から畳み込んでいくことで、$O(N (\log{N})^2)$ に高速化できる。

Python3

programming_algorithm/contest_history/atcoder/2026/0829_abc473.txt · 最終更新: by ikatakos
CC Attribution 4.0 International
Driven by DokuWiki Recent changes RSS feed Valid CSS Valid XHTML 1.0