トヨタシステムズプログラミングコンテスト2025(AtCoder Beginner Contest 431)F,G 問題メモ

F - Almost Sorted 2

問題文

  • 長さ $N$ の整数列 $A=(A_1,A_2,\ldots,A_N)$ および正整数 $D$ が与えられます。
  • $A$ を並び替えることで得られる整数列 $B=(B_1, B_2, \ldots, B_N)$ であって、次の条件を満たすものの個数を $998244353$ で割ったあまりを求めてください。
    • すべての $i\ (1\leq i\leq N-1)$ に対して $B_{i+1}\geq B_i-D$ が成り立つ。

制約

  • $2\leq N\leq 2\times 10^5$
  • $1\leq D\leq 10^6$
  • $1\leq A_i\leq 10^6$
  • 入力は全て整数

解法

挿入DP。
$B$ を空配列で初期化して、値の小さい順に挿入して構成していくことを考える。

例えば $9$ を挿入しようとしている時、$D=3$ なら、「末尾もしくは $6,7,8$ の前」にのみ、挿入できる。
大きくなる分にはいくら大きくなってもいいので、条件を満たしている配列にこのように挿入して、条件が崩れることは無い。

D=3          ↓       ↓ ↓ ↓    9の挿入可能位置
暫定B  [ 2  1  6  5  2  8  7  ]

$A_i$ は、暫定 $B$ に含まれる「$A_i-D$ 以上 $A_i$ 未満の値の個数 $+1$(末尾の分)」の挿入候補がある。
この数は、暫定 $B$ の並びに依らず全ての暫定配列で同じ個数だけある。

ただし同じ値が含まれる場合に注意が必要。数列として同じものは1つとして数える。

したがって、同じ値はまとめて挿入する。
$n$ 箇所の候補に $r$ 個の値を挿入するのは、${}_n \mathrm{H}_r$ で求められる。

Python3

G - One Time Swap 2

問題文

  • 長さ $N$ の整数列 $A=(A_1,A_2,\ldots,A_N)$ が与えられます。
  • 整数の組 $(l, r) \ (1\leq l\lt r\leq N)$ に対し、$f(l,r)$ を「$A$ の $l$ 番目の要素と $r$ 番目の要素を入れ替えることで得られる整数列」とします。
  • 長さ $\frac{N(N-1)}{2}$ の整数列の列 $B=(B_1,B_2,\ldots,B_{\frac{N(N-1)}{2}})$ を次の手順で生成します。
    • 空の列 $B=()$ を用意する。
    • 各整数の組 $(l, r)\ (1\leq l\lt r\leq N)$ に対し、$B$ に $f(l,r)$ を追加する。
    • $B$ を数列の辞書順でソートする。
  • $Q$ 個のクエリが与えられるので、順に処理してください。$i$ 番目のクエリは以下の通りです。
    • 整数 $k$ が与えられる。
    • $B_k=f(l,r)$ となるような整数の組 $(l, r) \ (1\leq l\lt r\leq N)$ を一つ求めそれを出力せよ。

制約

  • $2\leq N\leq 2\times 10^5$
  • $1\leq Q\leq 2\times 10^5$
  • $1\leq A_i\leq N$
  • $1\leq k\leq \frac{N(N-1)}{2}$
  • 入力は全て整数

解法

なかなか必要な手順が多い。
だが、1つ1つは $C~E$ 問題でも出そうなアルゴリズムの積み重ねなので、 「こういうものを求めたい」→「このアルゴリズムを使えば求められる」という小問題を1つ1つ解決していけば、突飛な発想や難しい知識は要らない。

まず「元の数列より辞書順で小さくなるのか、変わらないのか、大きくなるのか」で大きく場合分けする。

  • 小さくなる: $A_l \gt A_r$ なる $(l,r)$ → 転倒数だけ存在
  • 変わらない: $A_l = A_r$ なる $(l,r)$ → 同じ値のペア数だけ存在
  • 大きくなる: $A_l \lt A_r$ なる $(l,r)$

これで、どこに分類されるか大まかに分かる。

  • 変わらない場合は、適当な同じ値のペアが答え。
  • 大きくなる場合、反転させて小さくなる場合に帰着できる。
    • $k←\dfrac{N(N-1)}{2}-k$
    • $A←(N+1-A_1,N+1-A_2,...,N+1-A_N)$

小さくなる場合の中での順序を考える。$(l,r)$ が異なる2つが同じ配列になることは無いので、全ての大小関係が決まる。

  • 小さくなる値は早く来るほどよい。→ $l$ が小さい
  • $l$ が同じなら、交換で $l$ に来る値が小さいほどよい。→ $A_r$ が小さい
  • $l,A_r$ が同じなら、大きくなる値は遅いほどよい。→ $r$ が大きい

この比較順序で決めることができる。 とはいえ、$(l,r)$ の候補は $O(N^2)$ あるので、$(l,A_r,r)$ を全て列挙してソート……なんてことはできない。

とりあえず、$l$ ごとにクエリを分けて、以下の情報を $l$ の位置にメモっておく。

  • クエリ $i$ は、$l$ を左端とする中で $x$ 番目に小さい配列である

ここで、クエリ毎に適当な $l$ および $x$ は、 転倒数を求める過程で $L_i:=$「$i$ より右にあり、$A_i$ より小さい値の個数」を求めて累積和を取っておくと、 二分探索で求められる。$L_i$ は、つまり「$l=i$ として辞書順が小さくなる $(l,r)$ の個数」である。

その後、末尾から $(A_r,-r)$ をSortedSetなどに追加していく。
$l$ に到達したときに SortedSet 内にある $x$ 番目の $r$ が、対応する答えとなる。

Python3

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