目次
AtCoder Beginner Contest 471 F,G問題メモ
F - Concat (maximize)
問題文
- 数字のみからなる $N$ 個の文字列 $S_1,\ldots,S_N$ が与えられます。
- これらからちょうど $K$ 個を選んで好きな順序で連結した文字列を、(先頭の余分なゼロを除いて)十進表記の整数として解釈したときの値として考えられる最大値を求めてください。
- なお、全ての文字が
0である文字列を、先頭の余分なゼロを除いて十進表記の整数として解釈したときの値は $0$ であるとします。
制約
- $1 \leq K \leq N \leq 10^5$
- $S_i$ は数字のみからなる長さ $1$ 以上 $10$ 以下の文字列
- $N,K$ は整数である
解法
なんか嘘解法で通ってしまった。
先頭ゼロを考慮しない場合
まず、制約を簡単にした問題の解法を考える。
もし全ての $S_i$ の先頭がゼロでなければ、以下のように解ける。
- 桁(文字数)が多い方がいい。
- 文字数が同じなら辞書順が大きい方がいい。
よって、選ぶ $K$ 個の文字列は、$S_i$ を文字数毎に分類して (文字数, 同文字数の中での辞書順) が 大きい順に並べたときの上位 $K$ 個としてよい。 そうせず、つまり上位 $K$ 個に入る $S_i$ を使わず代わりに $S_j$ を使った場合、 答えとなる文字列の $S_j$ の部分を $S_i$ に置き換えると、必ず全体の値が大きくなることから正当性が言える。
使う $K$ 個の文字列の組が決まると、後は「結合結果の辞書順の最大化」を考えればよいことになる。
これは過去に出題例があり、以下の基準でソートすることで辞書順最大が達成できる。
- 比較関数を「$S_i+S_j \gt S_j+S_i$ なら $S_i \gt S_j$」としてソートする($+$ は文字列結合を表す)
降順ソート結果の順に結合したものが答えとなる。
先頭ゼロを考慮
先頭にゼロがあっても、「使う $K$ 個を決めると、そのソートは辞書順の最大化でいい」点は変わらない。
ただし使うのが必ずしも (文字数, 同文字数の中での辞書順) の上位 $K$ 個とは限らなくなる。
$S_i$ を (文字数, 同文字数の中での辞書順) でソートし、降順にindexを振り直したとする。
先頭に置く文字列 $S_x$ を固定する。
先頭以外の $K-1$ 個は、$S_x$ を除いた上での上位 $K-1$ 個としてよい。
$S_x$ が上位 $K$ 個に含まれるなら、つまり $x \le K$ なら、答えは先ほどと同様に上位 $K$ 個を並べ替えた結果でよい。
そうで無い場合、$S_x$ は、$S_{K+1}~S_N$ の中で整数として解釈したときの値が最も大きいものとしてよい。
この2通りを試すと十分である。
G - Caeser Syllables
問題文
- $0$ から $K-1$ までの番号で表される $K$ 種類の記号があります。
- 各記号は、母音であるか母音でないかのいずれかです。$V_j = 1$ のとき記号 $j$ は母音であり、$V_j = 0$ のとき記号 $j$ は母音ではありません。
- ある記号列の音節数を、その記号列における母音のみからなる連続部分列として極大なものの個数とします。
- 形式的には、長さ $N$ の記号列 $(a_1, \dots, a_N)$ の音節数を、$1 \leq l \leq r \leq N$ を満たす整数の組 $(l,r)$ であって以下をすべて満たすものの個数とします。
- 記号 $a_l, \dots, a_r$ はすべて母音である。
- $l \gt 1$ ならば、記号 $a_{l-1}$ は母音でない。
- $r \lt N$ ならば、記号 $a_{r+1}$ は母音でない。
- 長さ $N$ の数列 $A = (A_1, \dots, A_N)$ が与えられます。$k = 0, \dots, K-1$ について、以下の問題に答えてください。
- 長さ $N$ の記号列 $A' = (A'_1, \dots, A'_N)$ を、$A'_i := (A_i + k) \bmod K$ として定める。このとき、$A'$ の音節数はいくつか?
制約
- $1 \leq N \leq 7 \times 10^6$
- $1 \leq K \leq 2300$
- $0 \leq A_i \leq K-1$ ($1 \leq i \leq N$)
- $V_j \in \{0,1\}$ ($0 \leq j \leq K-1$)
- $0 \leq \mathrm{seed} \leq 2^{60} - 1$
- $1 \leq M \leq \min(N, 10^5)$
- $0 \leq b_i \leq K-1$ ($1 \leq i \leq M$)
- 入力される値はすべて整数
解法
要は、下の例における “V置換” したものに対してランレングス圧縮をした時の “1” の連続の個数を、$k=0,1,...,K-1$ のそれぞれで求めよ、という問題。
$k$ に対する答えは、「①$A'$ にある全ての母音の個数」-「②$A'$ で母音が連続している箇所の個数」となる。
主客転倒して、以下を考える。
- 各 $A_i$ が、$k=0,1,...,K-1$ それぞれの①に寄与する量
- 各 $(A_i,A_{i+1})$ が、$k=0,1,...,K-1$ それぞれの②に寄与する量
0 1 2 3 4 5
K=6 V=(1,1,0,0,1,0)
主客転倒①:
A' V置換 A1について、答えの k=0,...,5 に 1,0,1,1,0,0 を加算
k=0 (4,2,4,1) (1,0,1,1) A2について、答えの k=0,...,5 に 0,0,1,0,1,1 を加算
k=1 (5,3,5,2) (0,0,0,0) :
k=2 (0,4,0,3) (1,1,1,0) 主客転倒②:
k=3 (1,5,1,4) (1,0,1,1) (A1,A2)について、答えの k=0,...,5 から 0,0,1,0,0,0 を減算
k=4 (2,0,2,5) (0,1,0,0) (A2,A3)について、答えの k=0,...,5 から 0,0,1,0,0,0 を減算
k=5 (3,1,3,0) (0,1,0,1) (A3,A4)について、答えの k=0,...,5 から 1,0,0,1,0,0 を減算
$V$ 置換したものを縦に見る感じ。②は、V置換上で“1”が連続している部分が $1$ になる。
①はそのままだと $O(KN)$ だが、$A_i$ の値が同じもの同士でまとめて処理すれば $O(K^2)$ でできる。
②も同様に、答えへの寄与が同じパターンのものをまとめて処理したい。
ひとまず以下で $O(K^3)$ 解法ができる。もちろんそれはTLEとなる。
- 以下の $(d,a)$ を計算する。$(d,a)$ が同じものは寄与パターンが同じになる。
- $|A_i-A_{i+1}|$ と $K-|A_i-A_{i+1}|$ の小さい方を $d$ とする
- $A_i+d=A_{i+1} \bmod{K}$ となる場合は $a=A_i$、$A_{i+1}+d=A_{i} \bmod{K}$ となる場合は $a=A_{i+1}$ とする
- $(d,a)$ 毎に $k=0~K-1$ への寄与を計算する。
$(d,a)$ は要は、「$a$ と $a+d$ が隣り合っている」という情報に置換する中で、 $d$ は(ループしていて2通りに解釈できるので)小さい方を採用する、という意味合いになる。 必ずしも必要ないが、パターン数を減らすことで定数倍改善になる。
これを畳み込みで高速化する。
$(d,a)$ ではなく $d$ 毎にまとめて処理し、$a$ だけが異なるもの同士の計算をまとめる。
$d$ を固定し、以下の $X_0,...,X_{K-1}$ を計算する。
- $X_i:=V_i=1$ かつ $V_{i+d \bmod{K}}=1$ なら $1$、そうでないとき $0$
d=3 i 0 1 2 3 4 5 6 7 8 V 1 1 1 0 1 1 0 0 1 X 0 1 1 0 0 1 0 0 1 X: V[i] と V[i+d] がともに 1 なら 1
この時、$a=i$ の場合の②の寄与は、$X$ を $i$ から(ループして)見ていったものとなる。
a=2 なら、k=0,...,K-1 に対して i=2 からXを見ていって 1 0 0 1 0 0 1 0 1 と寄与する。 a=4 なら、k=0,...,K-1 に対して i=4 からXを見ていって 0 1 0 0 1 0 1 1 0 と寄与する。
よって、($d$ が同じ中で)$a$ 毎に要素数をカウントしたものを $Y$ とすると、
i 0 1 2 3 4 5 6 7 8 V 1 1 1 0 1 1 0 0 1 X 0 1 1 0 0 1 0 0 1 Y 3 1 4 1 5 9 2 6 5
- $k=0$ に対しては、$X_0 \cdot Y_0 + X_1 \cdot Y_1 + ... + X_{K-1} \cdot Y_{K-1}$ が寄与する
- $k=1$ に対しては、$X_1 \cdot Y_0 + X_2 \cdot Y_1 + ... + X_{0} \cdot Y_{K-1}$ が寄与する
- $k=2$ に対しては、$X_2 \cdot Y_0 + X_3 \cdot Y_1 + ... + X_{1} \cdot Y_{K-1}$ が寄与する
これは、添え字の“差”が一定となる要素の畳み込みなので、$X$ をもう1周繰り返し、$Y$ は並びを反転させてから畳み込むとよい。
$O(K \log{K})$ で $k=0,...,K-1$ の全ての②の寄与を求められる。
$d=0,...,K-1$ に対して同じことを行い、全体 $O(N+K^2 \log{K})$ で求められる。
高速化のために Codon を使おうとしたら、最初の入力のデコードにおいてちょっとハマった。
デコードでは符号無し64bit整数が求められていたが、Codonの通常のintは符号ありと解釈される。
(Codon特有の型を宣言すれば使えるのだが、演算で使用する全ての整数を同じ型にしないといけないので面倒だった)
何が問題かというと、state の64bit目が“1”になった時、そこから右シフトした時に新たに最上位に補われる値が、 符号無しなら “0”、符号ありなら “1” となるので、結果が変わってくる。 右シフトが発生する箇所では、例えば18bit右シフトするなら $64-18=46$ より下位46bitのマスク処理をすることで正常になった。

