ARC 067 F - Yakiniku Restaurants 別解

editorial で紹介されている解法は  O(N ^ 2+MN\log N) だが,別解として  O(N ^ 2 + MN) O(MN\log N) の 2 通りで解けたので ( N\leq 5000,\ M\leq 200 の制約だとあまり差は出なさそう).

問題

atcoder.jp

解法

基本的な考え方は,初めに訪れる焼肉店  l を降順に動かしながら差分を計算するというもの.

焼肉店  i-1 で終了する場合の最適値と焼肉店  i で終了する場合の最適値の差分  d_i を管理する. l を動かしたときに, d_i を高速に更新することが出来れば, l を固定した場合の最適値は  \displaystyle\max\left\{\sum_{i=l} ^ r d_i \;\middle|\;l\leq r\leq N\right\} として求まる.これは毎回愚直に求めても全体で  O(N ^ 2) である.

 \displaystyle
\begin{aligned}
X_j(l, r)&=\begin{cases} \displaystyle
\max _ {l \leq i \leq r} B _ {i, j} & \text{(if $\:1\leq l\leq r\leq N$)} \\
0 & \text{(otherwise)}
\end{cases},\\
Y_j(l, r)&=X_j(l, r)-X_j(l, r - 1)
\end{aligned}

とする.

 d の更新には, M 本の stack を用いる. j\;(1\leq j\leq M) 番目の stack を  S_j とし, S_j には (i,\;Y_j(l,i)) のペアを積んでいく.ただし, Y_j(l,i)=0 の場合は積まないものとする.

 l を動かすごとに,各  j に対して  S_j を以下の手続きにより更新する.

  1.  v:=B _ {l, j} とする.
  2.  S_j が空でなく,かつ  v\gt 0 であるならば,3 へ.そうでなければ,4 へ.
  3.  S_j の先頭  (x,\;y) を pop する. v\geq y ならば, v:=v-y として 2 へ.そうでなければ, S_j (x,\;y-v) を push して 4 へ.
  4.  S_j (l,\;B _ {l, j}) を push する.

つまるところ, B _ {i, j}\leq B _ {l, j} であるような  i に対応するペアを  S_j から取り除き,初めて  B _ {i, j} \gt B _ {l, j} となる  i に対してその差分を更新している.

 d に関しては, (x,\;y) が pop される度に  d _ x := d _ x - y と更新し,push される度に  d _ x := d _ x + y と更新する.また,移動距離を考慮して  d _ {l+1}:=d _ {l+1}-A _ l とする必要がある.

 S_j および  d の更新にかかる計算量は一見すると  \Theta(MN ^ 2 ) であるが,実は  O(MN) となっている.

[tex: O(MN)] の理由

 j に対して更新回数が全体で  O(N) となっていることが言えればよい.

手続きの 3. において push が起こる回数は,各  l に対して高々  1*1 で,合計  O(N) 回である.また, 4. において push が起こる回数も各  l に対して高々  1 回である.従って, S_j への push が起こる回数は  O(N) 回である. S_j から pop する回数は  S_j への push の回数以下なので,pop の回数も  O(N) である.

以上より,各  j に対して,手続き全体にかかる計算量は  O(N) である.

 S_j および  d の更新に  O(MN) \displaystyle\max\left\{\sum_{i=l} ^ r d_i \;\middle|\;l\leq r\leq N\right\} を求めるのに  O(N ^ 2) かかるので,結局全体  O(N ^ 2 + MN) でこの問題が解けた.

上の解法において, d の定義を累積和に変更して区間 add,区間 max の遅延セグ木に載せれば  O(MN\log N) となる.

実装 (Python)

提出リンク (AC)

def solve():
    N, M = map(int, input().split())
    A = [*map(int, input().split())]
    B = [[*map(int, input().split())] for _ in range(N)]
    d = [0] * N
    S = [[] for _ in range(M)]
    ans = 0
    for l in reversed(range(N)):
        if l < N - 1:
            d[l + 1] -= A[l]
        for j in range(M):
            v = B[l][j]
            while S[j] and v:
                x, y = S[j].pop()
                d[x] -= y
                if v >= y:
                    v -= y
                else:
                    S[j].append((x, y - v))
                    d[x] += y - v
                    break
            S[j].append((l, B[l][j]))
            d[l] += B[l][j]
        d_sum = 0
        for r in range(l, N):
            d_sum += d[r]
            ans = max(ans, d_sum)
    return ans

if __name__ == '__main__':
    print(solve())

*1:push が起こると即座に 4. へと移り,更新が終了するため

AtCoder Regular Contest 111 F - Do you like query problems?

めちゃくちゃ計算した.こんなんどうやったら時間内に解けるんだろう? クエリの読み替えが本質だったっぽくて,これに気付くことが出来ればあるいは...?みたいな感じかもしれない.ただ,今の自分では厳しい...

問題

atcoder.jp

解法

以降,操作対象の数列を  A とし,0-indexed とする.

列全体の動作を考えるのは難しいので,数列の添え字 t=0,\ldots,N-1 を固定して要素 1 つ 1 つに注目する.クエリの範囲は全部で \dfrac{N(N+1)}{2} 通りあるが,このうち  t が範囲内に含まれるのは  l=0,\ldots,t かつ  r=t,\ldots,N-1 のときで,即ち  (t+1)(N-t) 通り.これらの数を毎回書くと大変なので, I_t=(t+1)(N-t),  C=\dfrac{N(N+1)}{2} とおいておく.

解法 (泥臭いので読み飛ばしてもらってもよいです)

現在の  A_t の値を持てば DP が出来そうなので,DP テーブルを次のように定義してみる.

 \displaystyle
\begin{aligned}
X_t[i][x]=&\text{ クエリを $i$ 個処理した後に $A_t=x$ であるような全ての場合の,}\\
&\text{$A_t$ の寄与の総和}
\end{aligned}

 X_t[0] に関して,全ての  x に対して  X_t[0][x]=0 である.

 i+1 個目のクエリが \max\min\mathrm{sum} である場合のそれぞれについて, X_t[i+1][x] への遷移を考える (集める DP).

\max クエリ

 t がクエリの範囲内であるかどうかで更に場合分けをする.

 t が範囲内の場合

 t が範囲内となるような範囲の選び方は  I_t 通りである.

 \max クエリにおいて  A_t=x となるのは,

  • クエリの前は  A_t=0,\ldots,x-1 で, v=x のクエリが飛んでくる場合
  • クエリの前は  A_t=x で, v=0,\ldots,x のクエリが飛んでくる場合

の 2 つのケースが考えられる.従って,このケースの寄与は次の式 (1) で表される.

 \displaystyle
I_t\left(\sum_{y=0}^{x-1}X_t[i][y]+(x+1)\cdot X_t[i][x]\right)\tag{1}

 t が範囲外の場合

 t が範囲内となるような範囲の選び方は  C-I_t 通りである.

 t が範囲外の場合は元から  A_t=x である必要があり, v の値は関係ない.従って,このケースの寄与は次の式 (2) で表される.

 \displaystyle
(C-I_t)\cdot M\cdot X_t[i][x]\tag{2}

 \min クエリ

 t がクエリの範囲内であるかどうかで更に場合分けをする.

 t が範囲内の場合

 t が範囲内となるような範囲の選び方は  I_t 通りである.

 \min クエリにおいて  A_t=x となるのは,

  • クエリの前は  A_t=x,\ldots,M-1 で, v=x のクエリが飛んでくる場合
  • クエリの前は  A_t=x で, v=x+1,\ldots,M-1 のクエリが飛んでくる場合

の 2 つのケースが考えられる.従って,このケースの寄与は次の式 (3) で表される.

 \displaystyle
I_t\left(\sum_{y=x}^{M-1}X_t[i][y]+(M-x-1)\cdot X_t[i][x]\right)\tag{3}

 t が範囲外の場合

 \max クエリにおいて  t が範囲外の場合と全く同様であり,このケースの寄与は次の式 (4) で表される.

 \displaystyle
(C-I_t)\cdot M\cdot X_t[i][x]\tag{4}

\mathrm{sum} クエリ

まず, \mathrm{sum} クエリにおいて  A_t の値は変化しないので,任意の範囲に対して  X_t[i][x] からの寄与があり, X_t[i][y] ( y\neq x) からの寄与はない.従って, この場合の寄与は式 (5) で表される.

 \displaystyle
C\cdot X_t[i][x]\tag{5}

また, \mathrm{sum} クエリにおいてはクエリ自体による寄与が存在する. t が範囲内である必要があるので,これは以下のように書ける.

 \displaystyle
x\cdot I_t\cdot \text{(クエリを $i$ 個処理した後に $A_t=x$ であるような場合の数)}

ここで, \text{(クエリを $i$ 個処理した後に $A_t=x$ であるような場合の数)} が分からないので別で求める必要がある.

取り敢えずこの値を  Y_t[i][x] とおけば,クエリ自体の寄与は次の式  (6) で表される.

 \displaystyle
x\cdot I_t\cdot Y_t[i][x]\tag{6}

以上 (1),\ldots,(6) より, X_t[i+1][x] の表示は式 (7) のようになる.

 \displaystyle
\require{color}
\begin{align}
X_t[i+1][x]&=
\underset{\textstyle\text{$\max$ クエリの寄与}}{\textcolor{red}{\underline{\textcolor{black}
{
I_t\left(\sum_{y=0}^{x-1}X_t[i][y]+(x+1)\cdot X_t[i][x]\right)+(C-I_t)\cdot M\cdot X_t[i][x]
}}}}\\
&+
\underset{\textstyle\text{$\min$ クエリの寄与}}{\textcolor{blue}{\underline{\textcolor{black}
{
I_t\left(\sum_{y=x}^{M-1}X_t[i][y]+(M-x-1)\cdot X_t[i][x]\right)+(C-I_t)\cdot M\cdot X_t[i][x]
}}}}\\
&+
\underset{\textstyle\text{$\mathrm{sum}$ クエリの寄与}}{\textcolor{green}{\underline{\textcolor{black}
{
C\cdot X_t[i][x]+x\cdot I_t\cdot Y_t[i][x]
}}}}\\
&=I_t\sum_{y=0}^{M-1}X_t[i][y]+( (2M+1) \cdot C-M \cdot I_t)\cdot X_t[i][x]+x\cdot I_t\cdot Y_t[i][x] \tag{7}
\end{align}

そして,この問題で求めたいのは  X_t[i][x] x について和をとったものなので, S_t を次のように定義する.

 \displaystyle
S_t[i]=\sum_{x=0}^{M-1}X_t[i][x]\tag{8}

 (7) の両辺を  x について和をとって,式  (9) を得る.

 \displaystyle
S_t[i+1]=(2M+1)\cdot C\cdot S_t[i]+I_t\sum_{x=0}^{M-1}x\cdot Y_t[i][x]\tag{9}

良い感じになってきたが, Y_t が分からないのでこれ以上は計算が進まない.そこで,以下は  Y_t を求める.

まず, Y_t[0] は定義より次のようになる.

 \displaystyle
Y_t[0][x]=\begin{cases}
1&\text{(if $x=0$)}\\
0&\text{(otherwise)}
\end{cases}

そして, Y_t X_t とほぼ同様の漸化式を立てることが出来る.導出過程は省略し,結果を式  (10) に示す.式  (7) との違いは最終項の有無のみである.

 \displaystyle
\begin{align}
Y_t[i+1][x]&=
\underset{\textstyle\text{$\max$ クエリの寄与}}{\textcolor{red}{\underline{\textcolor{black}
{
I_t\left(\sum_{y=0}^{x-1}Y_t[i][y]+(x+1)\cdot Y_t[i][x]\right)+(C-I_t)\cdot M\cdot Y_t[i][x]
}}}}\\
&+
\underset{\textstyle\text{$\min$ クエリの寄与}}{\textcolor{blue}{\underline{\textcolor{black}
{
I_t\left(\sum_{y=x}^{M-1}Y_t[i][y]+(M-x-1)\cdot Y_t[i][x]\right)+(C-I_t)\cdot M\cdot Y_t[i][x]
}}}}\\
&+
\underset{\textstyle\text{$\mathrm{sum}$ クエリの寄与}}{\textcolor{green}{\underline{\textcolor{black}
{
C\cdot Y_t[i][x]
}}}}\\
&=I_t\sum_{y=0}^{M-1}Y_t[i][y]+( (2M+1) \cdot C-M \cdot I_t)\cdot Y_t[i][x] \tag{10}
\end{align}

ここで, Y_t[i] の総和は実は問題文にその答えが書かれており,

 \displaystyle
\sum_{x=0}^{M-1}Y_t[i][x]=( (2M+1)\cdot C)^i \tag{11}

である (もちろん普通に求めてもよい).従って,更に式  (10) の計算を進めることができ,

 \displaystyle
Y_t[i+1][x]=I_t( (2M+1)\cdot C)^i +( (2M+1) \cdot C-M \cdot I_t)\cdot Y_t[i][x]

となる.これは以下のようにして  x について独立に解くことができる.

 \displaystyle
\frac{Y_t[i+1][x]}{(a-b_t)^{i+1}}=\frac{Y_t[i][x]}{(a-b_t)^{i}}+\frac{I_t}{a-b_t}\left(\frac{a}{a-b_t}\right)^i\quad
\left(\begin{aligned}
a &= (2M+1)\cdot C,\\
b_t &= M \cdot I_t.
\end{aligned}\right)
 \displaystyle
\begin{aligned}
Y_t[i][x]&=
(a-b_t)^i\left(Y_t[0][x]+\frac{I_t}{a-b_t}\sum_{j=0}^{i-1}\left(\frac{a}{a-b_t}\right)^j\right)\\
&=(a-b_t)^i\cdot Y_t[0][x]+\frac{1}{M}\left(a^i-(a-b_t)^i\right)\\
&=\begin{cases}
\dfrac{1}{M}\left(a^i-(a-b_t)^i\right)+(a-b_t)^i & \text{(if $x=0$)}\\
\dfrac{1}{M}\left(a^i-(a-b_t)^i\right)& \text{(otherwise)}
\end{cases}
\end{aligned}

さて, Y_t の具体的な表示が得られたので式  (9) に代入する.

 \displaystyle
\begin{align}
S_t[i+1]
&=a\cdot S_t[i]+I_t\sum_{x=0}^{M-1}x\cdot Y_t[i][x]\\
&=a\cdot S_t[i]+I_t\sum_{x=\textcolor{red}{1}}^{M-1}x\cdot Y_t[i][x]\\
&=a\cdot S_t[i]+I_t\sum_{x=1}^{M-1}x\cdot \dfrac{1}{M}\left(a^i-(a-b_t)^i\right)\\
&=a\cdot S_t[i]+\dfrac{I_t}{M}\left(a^i-(a-b_t)^i\right)\sum_{x=1}^{M-1}x\\
&=a\cdot S_t[i]+\dfrac{I_t\cdot (M-1)}{2}\left(a^i-(a-b_t)^i\right)
\end{align}

この形の漸化式は両辺を  a ^ {i+1} で割ると解けて,

 \displaystyle
\begin{align}
\frac{S_t[i]}{a^{i}}
&=\frac{S_t[i-1]}{a^{i-1}}+\frac{I_t\cdot (M-1)}{2a}\left(1-\left(\frac{a-b_t}{a}\right)^{i-1}\right)\\
&=S_t[0]+\frac{I_t\cdot (M-1)}{2a}\sum_{j=0}^{i-1}\left(1-\left(\frac{a-b_t}{a}\right)^j\right)\\
&=\frac{I_t\cdot (M-1)}{2a}\left(i+\frac{a}{b_t}\left(\left(\frac{a-b_t}{a}\right)^i-1\right)\right)\quad(\because X_t[0][x]=0,\;\forall x),\\
S_t[i]&=\frac{I_t\cdot (M-1)}{2}\left(i\cdot a^{i-1}+\frac{(a-b_t)^i-a^i}{b_t}\right)\tag{12}
\end{align}

となる.式  (12) より,求める答えは  (13) である.

 \displaystyle
\sum_{t=0}^{N-1}S_t[Q]=\sum_{t=0}^{N-1}\frac{I_t\cdot (M-1)}{2}\left(Q\cdot a^{Q-1}+\frac{(a-b_t)^Q-a^Q}{b_t}\right)\tag{13}

ここで,

 \displaystyle
\begin{gather}
C=\frac{(N+1)N}{2},& I_t=(t+1)(N-t),\\
a=(2M+1)\cdot C,&b_t=M\cdot I_t
\end{gather}

である.適切に計算すれば, (13) O ( N (\log Q + \log P) で求めることが出来る.

これだと道のりがかなり長く,あまり綺麗ではありません.そこで,クエリの解釈の仕方を変えてみます.

 A_t=x のとき, \mathrm{chmax}(A_t,v) によって  A_t=v となるのは, v=x,\ldots,M-1 のときです.一方, \mathrm{chmin}(A_t,v) によって  A_t=v となるのは, v=0,\ldots,x-1 のときです.そして,その他の場合では  A_t は変化しません ( v=x のケースは都合のいいように解釈をしてもよいです).ここで重要なのは, x の値によらず, A_t=0,\ldots,M-1 へと変化するのが  1 通りずつあり,何も変化しないのが  M 通りあるということです.

つまり,\mathrm{chmax} および \mathrm{chmin} をひとまとめにして考えると,これらは次の 2 つの操作に置き換えられます.

  1. \mathrm{update}(l,r,v) :  A_l=\cdots=A_r=v へと更新する
  2. \mathrm{nop}(l,r,v) : 何もしない

ここまで来れば, i 回の操作後に  A_t=x となる場合の数  Y_t[i][x] を一発で求められます.まず, A_t \mathrm{update} クエリの対象となるのは  b_t:=I_t\cdot M 通りです.また,クエリのパターン数は  a:=(2M+1)\cdot C 通りだったので, \mathrm{update} クエリの対象とならないのは  a-b_t 通りということになります.

従って, i 回の操作において一回も  \mathrm{update} クエリの対象とならないのは  (a - b _ t) ^ i 通りです.このとき,操作後の  A_t の値は  0 です.

そして, i 回の操作において一回以上  \mathrm{update} クエリの対象となるのは  a ^ i - (a - b _ t) ^ i 通りです.このとき,操作後に  A_t=v\;(v=0,\ldots,M-1) となる場合の数は  v によりません.つまり, A_t=v となる場合の数は  \dfrac{1}{M}\left(a ^ i - (a - b _ t) ^ i\right) 通りです.

以上より, Y_t[i][x] は以下のように求められ,これは上で示した結果と一致しています.

 \displaystyle
Y_t[i][x]=\begin{cases}
\dfrac{1}{M}\left(a^i-(a-b_t)^i\right)+(a-b_t)^i & \text{(if $x=0$)}\\
\dfrac{1}{M}\left(a^i-(a-b_t)^i\right)& \text{(otherwise)}
\end{cases}

そして,任意の i 回の操作における  \mathrm{sum} クエリの出力の総和  S_t[i] に関する漸化式 :

 \displaystyle
S_t[i+1]=a\cdot S_t[i]+I_t\sum_{x=0}^{M-1}x\cdot Y_t[i][x]

に代入すると解ける形になるので,解いて  \displaystyle\sum_{t=0} ^ {N-1} S_t[Q] を計算すれば AC が取れます (代入と漸化式の計算パートは折り畳み部分に書いています).

実装 (Python)

提出リンク (AC)

P = 998244353

N, M, Q = map(int, input().split())

inv_2 = pow(2, -1, P)
C = (N + 1) * N * inv_2 % P
I = [(t + 1) * (N - t) % P for t in range(N)]
a = (2 * M + 1) * C % P
b = [M * I[t] % P for t in range(N)]

ans = 0
for t in range(N):
    v1 = I[t] * (M - 1) % P * inv_2 % P
    u1 = Q * pow(a, Q - 1, P) % P
    u2 = (pow(a - b[t], Q, P) - pow(a, Q, P)) * pow(b[t], -1, P) % P
    ans += v1 * (u1 + u2) % P
print(ans % P)

キャディプログラミングコンテスト2021(AtCoder Beginner Contest 193)

F はすぐ解けたんだけど,E で詰まりに詰まった...

A - Discount

 \dfrac{100(A-B)}{A} が答え.

B - Play Snuke

 \displaystyle \min _ {\overset{\scriptstyle 1\leq i\leq N,}{A_i\lt X_i}}P _ i が答え.

1 行で書いてみた (Python).

print(min(p for _, p, _ in L) if (L := [*filter(lambda t: t[0] < t[2], ([*map(int, input().split())] for _ in range(int(input()))))]) else -1)

C - Unexpressed

 b\geq 2 より, a ^ b\leq N を満たす  a は高々  \lfloor\sqrt{N}\rfloor である.また, a\geq 2 を固定すると, a ^ b\leq N を満たす  b は高々  \lfloor\log _ a N\rfloor である.

従って, a\geq 2,  b\geq 2,  a ^ b\leq N を満たす  a,  b の全探索は  O(\sqrt{N}\log N) 時間で可能である.

注意しないといけないのは  2 ^ 4 = 4 ^ 2 のような重複を除く必要があるという点で,これは既に現れた値を集合型で管理すれば可能である.

D - Poker

スコア計算式はハリボテで,その実は # の中身を全通り試して確率を計算する問題.実装が面倒.

既に確認されている  8 枚のカードを除いた  9K-8 枚からランダムに残りの  2 枚を引くと考える. 9K-8 枚のカードのうち, i が書かれたカードの枚数を  X_i とすると,高橋君が  i,青木君が  j を引く確率  p_{i, j} は次のように表される.


p_{i,j} = \begin{cases}
\dfrac{X_i\cdot X_j}{(9K-8)(9K-9)} & \text{(if $i\neq j$)}\\
\dfrac{X_i\cdot (X_i-1)}{(9K-8)(9K-9)}  & \text{(if $i = j$)}
\end{cases}

引いた  i,  j を加味したスコアを計算し,高橋君の方が高ければ  p_ {i, j} を答えに加算すればよい.

E - Oversleeping

時刻  t\geq 0 に高橋君が降車に成功することと,次の 2 つが成り立つことは同値である.


X\leq (t\bmod 2X+2Y) \lt X+Y,

P\leq (t\bmod P+Q)\lt P+Q.

制約をよく見るとそれぞれの区間長が小さいので,全探索ができる.つまり, A=(t\bmod 2X+2Y),  B=(t\bmod P+Q) と決め打って以下のような連立合同式へと変換する.


\begin{cases}
t\equiv A\pmod {2X+2Y}\\
t\equiv B\pmod {P+Q}
\end{cases}

連立合同式を満たす最小の非負整数は中国剰余定理を用いて求められる.

F - Zebraness

令和 ABC でフローが出題されたのは初めて?

二値変数がいくつかあって,その関係によって変化するコストを最小化する問題はグラフの最小カットに帰着できる場合がある (燃やす埋める).

今回の問題では,下図のような辺の張り方をすればよい.ただし,u と v は隣接する ? マスである.

図: 辺の張り方

SOMPO HD プログラミングコンテスト2021(AtCoder Beginner Contest 192)

43:47 + 0:00 全完で全体 46 位,日本人 39 位でした.賞金ワンチャンないかな...?

A - Star

コイン 100 枚で 1 up するアレかな? 100-(X\bmod 100) が答え.

B - uNrEaDaBlE sTrInG

char[] s = sc.nextChars();
boolean y = true;
for (int i = 0; i < s.length; i += 2) {
    y &= Character.isLowerCase(s[i]);
}
for (int i = 1; i < s.length; i += 2) {
    y &= Character.isUpperCase(s[i]);
}
pw.println(y ? "Yes" : "No");

C - Kaprekar Number

やるだけなんだけど実装方針が決まらず迷子に.

long n = sc.nextInt();
int k = sc.nextInt();
long x = n;
for (int i = 0; i < k; i++) {
    if (x == 0) break;
    long _x = x;
    int p = 0;
    while (_x > 0) {
        _x /= 10;
        p += 1;
    }
    int[] c = new int[p];
    int q = 0;
    _x = x;
    while (_x > 0) {
        c[q++] = (int) (_x % 10);
        _x /= 10;
    }
    Arrays.sort(c);
    long g1 = 0, g2 = 0;
    for (int j = 0; j < p; j++) {
        g1 = g1 * 10 + c[j];
        g2 = g2 * 10 + c[p - j - 1];
    }
    x = g2 - g1;
}
pw.println(x);

D - Base n

順位表が真っ赤っか.雑にやるとオーバーフローするのと,桁数が 1 の時がコーナーケースになることが原因?

二分探索で毎回  M 以下になるかを調べればいい.制約は厳しくないので,BigInteger を使っても TL には間に合いそうということで投げると通る.

char[] _x = sc.nextChars();
int n = _x.length;
int[] x = new int[n];
for (int i = 0; i < n; i++) {
    x[i] = _x[i] - '0';
}
int d = IntArrays.max(x);
long m = sc.nextLong();
if (n == 1) {
    pw.println(x[0] <= m ? 1 : 0);
    return;
}
BigInteger bm = BigInteger.valueOf(m);
long l = d, r = m + 1;
Out: while (r - l > 1) {
    BigInteger t = BigInteger.valueOf((l + r) >> 1);
    BigInteger v = BigInteger.ZERO;
    for (int i = 0; i < n; i++) {
        v = v.multiply(t).add(BigInteger.valueOf(x[i]));
        if (v.compareTo(bm) > 0) {
            r = t.longValue();
            continue Out;
        }
    }
    l = t.longValue();
}
pw.println(l - d);

E - Train

Dijkstra をやる.ボトルネック部に剰余演算が追加されて重くなりそうだったので,隣接リストを配列で持って定数倍高速化をした.結果的に多分要らなかったんだけど,万が一の TLE を考えると払ってもいい対価だったと思う.

public static void solve(ExtendedScanner sc, FastPrintStream pw) {
    int n = sc.nextInt();
    int m = sc.nextInt();
    int x = sc.nextInt() - 1;
    int y = sc.nextInt() - 1;
    Edge[] edges = new Edge[m];
    int[] cnt = new int[n];
    for (int i = 0; i < m; i++) {
        int u = sc.nextInt() - 1;
        int v = sc.nextInt() - 1;
        long t = sc.nextLong();
        int k = sc.nextInt();
        
        edges[i] = new Edge(u, v, k, t);
        cnt[u]++;
        cnt[v]++;
    }
    Edge[][] g = new Edge[n][];
    for (int i = 0; i < n; i++) {
        g[i] = new Edge[cnt[i]];
    }
    for (var e : edges) {
        int u = e.u;
        int v = e.v;
        g[u][--cnt[u]] = e;
        g[v][--cnt[v]] = new Edge(v, u, e.k, e.t);
    }

    long[] d = new long[n];
    PriorityQueue<State> pq = new PriorityQueue<>();

    java.util.Arrays.fill(d, Const.LINF);
    d[x] = 0;
    pq.add(new State(x, 0L));
    while (pq.size() > 0) {
        State st = pq.poll();
        int u = st.v;
        if (st.d != d[u]) continue;
        for (var e : g[u]) {
            int v = e.v;
            int k = e.k;
            long c = e.t;
            long r = d[u] % k;
            long du = r == 0 ? d[u] : d[u] + k - r;
            if (du + c < d[v]) {
                d[v] = du + c;
                pq.add(new State(v, d[v]));
            }
        }
    }

    if (d[y] == Const.LINF) {
        pw.println(-1);
    } else {
        pw.println(d[y]);
    }
}

private static final class State implements Comparable<State> {
    final int v;
    final long d;
    State(int v, long d) {this.v = v; this.d = d;}
    public int compareTo(State s) {return d == s.d ? v - s.v : d > s.d ? 1 : -1;}
}

static class Edge {
    int u, v;
    int k;
    long t;
    Edge(int u, int v, int k, long t) {
        this.u = u;
        this.v = v;
        this.k = k;
        this.t = t;
    }
}

F - Potion

選んだ素材の集合を  S k=|S| とする.

制約より  \displaystyle\sum A_i\leq X が保証されているため, \displaystyle \sum_{i\in S}A_i\equiv X \pmod k であることが丁度  X となるための必要十分条件.このとき  \displaystyle \frac{X - \sum _ {i\in S}A _ i}{k} の時間が掛かる.

 k=1,\ldots,N に対して,次の DP を考える.

\displaystyle
\mathrm{dp}[i][j][m]=\max\left\{
  \sum_{x\in S}A_x\;\middle |\;
  S\subset\{1,\ldots,i\},\;|S|=j,\;\sum_{x\in S}A_x\equiv m\;(\mathrm{mod}\;k)
\right\}

この DP は以下のようにして計算でき, \dfrac{X - \mathrm{dp}[n][k][X\bmod k]}{k} k を固定した場合の最適値である.

\displaystyle
\mathrm{dp}[0][j][m]=\begin{cases}
0 & (j=0,\;m=0)\\
-\infty & (\text{otherwise})
\end{cases}
\displaystyle
\mathrm{dp}[i][j][m]=\max\{\mathrm{dp}[i-1][j][m],\;\mathrm{dp}[i-1][j-1][m-A_i\bmod k]+A _ i\}

計算量は  O(N ^ 4) だが,適切に  j,  m の範囲を絞れば定数倍が 1 よりも小さくなり,余裕を持って通すことができる.

AtCoder Regular Contest 006 D - アルファベット探し

特に学び的なことはなかったけど,かなり面倒そうに見える問題が楽に解けたので.

問題は ↓ から.

atcoder.jp

解法

# マスから上下左右 + 斜め 4 方向の合計 8 方向に存在する # マスへ辺を張ったグラフを考えると,A, B, C はすべて連結であることに着目する.等倍の A, B, C に対応する連結成分の大きさはそれぞれ 12, 16, 11 で全て異なるので,これを使って判定することを考える.なお,制約より図形が重なることはないので 2 つ以上の文字が同じ連結成分に属することはない.

連結成分の大きさが  X のアルファベットを  k 倍拡大すると,拡大後の連結成分の大きさは  k ^ 2X となる.

従って,Union-Find や幅優先探索により各連結成分の大きさ  S を取得し,

 S=k ^ 2X\quad k\in\mathbb{N} ^ +,\; X\in\{12,16,11\}

を満たす  k,  X を決定する問題へと落とすことができた.

 S が持つ素因数  11 および  3 の数に着目することで, X および対応するアルファベットは次のように求められる.

  1.  S=1 のとき: 空マス.
  2.  S が素因数  11 を奇数個持つとき:  X=11 で,対応するアルファベットは C.
  3.  S が素因数  3 を奇数個持つとき:  X=12 で,対応するアルファベットは A.
  4. 上記以外:  X=16 で,対応するアルファベットは B.

以上より,幅優先探索を用いることにすれば全体  O(HW) でこの問題を解くことが出来る.

重複数珠順列

ほぼ自分用のメモなので色々雑かも.

問題

問題文

円周上に  N 個の玉が等間隔で並んでおり,それぞれの玉に色を塗ることを考える.色は  1,\dots,M M 色があり,複数の玉を同じ色で塗ってもよいし,使われない色があってもよい.玉に色を塗る方法の数を  \bmod 10 ^ 9+7 で求めよ.ただし,回転や裏返しによって一致する塗り方は  1 通りとして数えるものとする.

制約

  •  1\leq N\leq 10 ^ 9
  •  1\leq M\leq 10 ^ 9

解法

コーシー・フロベニウスの定理 (バーンサイド補題とも) が適用できる (バーンサイドの補題 - Wikipedia).

以下,適当な玉を起点として反時計回りに玉  0,\dots,N-1 と名前を付ける.また,玉  i に塗る色を  C_i\in\{1,\dots,M\} とする.回転および裏返しに対応する群は二面体群  D_N であり,いま  D_N は集合

 X=\{(C_0,\dots,C_{N-1})\mid C_i\in\{1,\dots,M\}\;\text{for$\;i=0,\dots, N-1$}\}

に作用している.

円を反時計回りに  \dfrac{2\pi}{N} 回転する作用素 \sigma\in D_N とおく. k=0,\dots,N-1 に対して  \sigma ^ k の作用前後で一致する条件を考えると,

 C_i=C_{i+\gcd(k,N)\bmod N},\quad i=0,\dots,N-1

が成り立つことが必要十分条件であると分かる.従って,

 |X ^ {\sigma ^ k}|=M ^ {\gcd(k,N)},\quad k=0,\dots,N-1

である.

続いて,円を玉  0 と円の中心を通る軸  L で折り返す作用素 \tau\in D_N とおく.  D_N は, \sigma \tau を用いて次のように表示できる.

 \begin{aligned}
D_N
&=\langle \sigma,\tau \mid \sigma ^ N=\tau ^ 2=\sigma\tau\sigma\tau=e \rangle\\
&=\{e,\sigma,\dots,\sigma ^ {N-1},\tau,\tau\sigma,\dots,\tau\sigma ^ {N-1}\}.
\end{aligned}

 \tau\sigma ^ k\in D_N は,円の中心を中心として軸  L \dfrac{k\pi}{N} だけ回転した対称軸に対応する.

 N が奇数であれば,対称軸は必ずある頂点とその向かいの辺の中点を通る直線となるので,

 |X ^ {\tau\sigma ^ k}|=M ^ {\frac{N+1}{2}},\quad k=0,\dots,N-1

である.

一方, N が偶数であれば,対称軸が向かい合う二頂点を通るケースと向かい合う二辺の中点を通るケースが交互に現れる.従って,

 |X ^ {\tau\sigma ^ k}|=
\begin{cases}
M ^ {\frac{N}{2}+1} & k=0,2,\dots,N-2\\
M ^ {\frac{N}{2}} & k=1,3,\dots,N-1
\end{cases}

である.

以上の結果から,問題の答えを  f(N,M) するとこれは以下のように表せる.

 f(N,M)=
\begin{cases}
        \displaystyle\frac{1}{2N}\left(\sum_{i=1}^NM^{\gcd(N,i)}+NM^{\frac{N+1}{2}}\right) & \text{$N$ が奇数のとき}\\
        \displaystyle\frac{1}{2N}\left(\sum_{i=1}^NM^{\gcd(N,i)}+\frac{NM^{\frac{N}{2}}}{2}+\frac{NM^{\frac{N}{2}+1}}{2} \right) & \text{$N$ が偶数のとき}
\end{cases}

しかし,このまま計算すると  O(N\log N) となってしまうので工夫する必要がある.

注目すべきは, \gcd(N,i) の値が取り得る値は  N の約数に限られるという点である.そこで, g=\gcd(N,i) の値ごとに  M ^ g をまとめて足し上げることを考える.つまり,

 \displaystyle
\sum_{i=1}^NM^{\gcd(N,i)}=\sum_{g\mid N}\#\{i\in\{1,\dots,N\}\mid \gcd(N,i)=g\}\cdot M ^ g

という式変形を行う.以下, d=N/g とする ( g\mid N より, d は整数であることに注意).

ここで, \gcd(i,N)=g となるためには  g\mid i が必要であり,また, g\mid N なので

 \displaystyle\begin{aligned}
&\#\{i\in\{1,\dots,N\}\mid \gcd(N,i)=g\}\\
=&\#\{k\in\{1,\dots,d\}\mid \gcd(N,kg)=g\}\\
=&\#\{k\in\{1,\dots,d\}\mid \gcd(d,k)=1\}\\
=&\phi(d)
\end{aligned}

である.ただし, \phiオイラーのトーシェント関数である.以上より,次が成り立つ.

 \displaystyle
\sum _ {i=1} ^ NM ^ {\gcd(N,i)}=\sum_{g\mid N} \phi(d)\cdot M ^ g
.

あとは, \phi(d) を求めればよい. \mathrm{PF}(d) d の素因数全体の集合とすると

 \displaystyle\begin{aligned}
\phi(d)=d\prod_{p\in \mathrm{PF}(d)}\left(1-\dfrac{1}{p}\right)
\end{aligned}

であり, \mathrm{PF}(d) は約数列挙の要領で  O(\sqrt{d}) で求められるので,結局  \phi(d) O(\sqrt{d}) で求められる.

 N\leq 10 ^ 9 の制約の下で  N の約数は最大でも  1344 個なので,和の計算は十分高速に行うことができ,この問題を解くことが出来た.

結論

 f(N,M)=
\begin{cases}
        \displaystyle\frac{1}{2N}\left(\sum_{g\mid N} \phi(N/g)\cdot M^{g}+NM^{\frac{N+1}{2}}\right) & \text{$N$ が奇数のとき}\\
        \displaystyle\frac{1}{2N}\left(\sum_{g\mid N} \phi(N/g)\cdot M^{g}+\frac{NM^{\frac{N}{2}}}{2}+\frac{NM^{\frac{N}{2}+1}}{2} \right) & \text{$N$ が偶数のとき}
\end{cases}

なお,裏返しを考慮しない場合の答え  g(N,M) は,そのまま裏返し部分を消せばよく,次のように求められる.

 \displaystyle g(N,M)=\frac{1}{N}\sum_{g\mid N} \phi(N/g)\cdot M^{g}

AtCoder Beginner Contest 190

f:id:suisen_kyopro:20210130213637p:plain

2 連続橙 perf で黄色復帰!!めでたい.

A - Very Very Primitive Game

  •  A\gt B なら高橋君の勝ち
  •  A\lt B なら青木君の勝ち
  •  A=B なら  C=0 のとき青木君, C=1 のとき高橋君の勝ち
int a = sc.nextInt();
int b = sc.nextInt();
int c = sc.nextInt();
if (a > b) {
    pw.println("Takahashi");
} else if (b > a) {
    pw.println("Aoki");
} else {
    pw.println(c == 0 ? "Aoki" : "Takahashi");
}

B - Magic 3

 X _ i \lt S かつ  Y _ i \gt D を満たす  i が存在すれば Yes, そうでなければ No

int n = sc.nextInt();
long s = sc.nextLong();
long d = sc.nextLong();
boolean ok = false;
for (int i = 0; i < n; i++) {
    long x = sc.nextLong();
    long y = sc.nextLong();
    ok |= x < s && y > d;
}
pw.println(ok ? "Yes" : "No");

C - Bowls and Dishes

 K 人の人がそれぞれ  C_i D_i のどちらに置くかの  2 ^ K 通りを全探索し,幾つの条件が満たされているかを愚直に数えてその最大値を求めればよい.これは全体  O(2 ^ K (N+K+M)) で計算できる.

int n = sc.nextInt();
int m = sc.nextInt();
int[] a = new int[m];
int[] b = new int[m];
for (int i = 0; i < m; i++) {
    a[i] = sc.nextInt() - 1;
    b[i] = sc.nextInt() - 1;
}
int k = sc.nextInt();
int[][] cd = new int[k][2];
for (int i = 0; i < k; i++) {
    cd[i][0] = sc.nextInt() - 1;
    cd[i][1] = sc.nextInt() - 1;
}
int max = 0;
for (int s = 0; s < 1 << k; s++) {
    boolean[] put = new boolean[n];
    for (int i = 0; i < k; i++) {
        put[cd[i][(s >> i) & 1]] = true;
    }
    int cnt = 0;
    for (int i = 0; i < m; i++) {
        if (put[a[i]] && put[b[i]]) cnt++;
    }
    max = Math.max(max, cnt);
}
pw.println(max);

D - Staircase Sequences

初項  A,公差  D,項数  X の等差数列の和は  \dfrac{1}{2}X(2A+(X-1)D) である.いま, D=1 で固定なので, N=\dfrac{1}{2}X(2A+X-1) を満たす整数  A,  X の組の個数が求まればよい.ただし, X は項数なので  X\gt 0 である.

両辺に  2 を掛けてやると,条件式は  2N=X(2A+X-1) と書き直せるので, X の値としてあり得るのは  2N の正の約数のみであることが分かる.

 X 2N の約数として, Y=2N/X と置くと  Y もまた整数であり, Y=2A+X-1 が成り立つ.これを  A について解くと, A=\dfrac{1}{2}(Y-X+1) となるので, Y-X+1 が偶数であれば  A は整数となり条件を満たす.( A の範囲に関する制約は存在しない)

 \sqrt{2N} まで試し割りして約数を列挙すれば,全体  O(\sqrt{N}) で解くことが出来る.

long n = sc.nextLong() * 2;
long cnt = 0;
for (long x : MathUtil.divisors(n)) {
    long y = n / x;
    cnt += 1 ^ ((y - x + 1) & 1);
}
pw.println(cnt);

E - Magical Ornament

少し隠されてはいるが,TSP を解けばよいことが分かる.

 (A_i, B_i) を張ったグラフを作って各  s=1,\dots,K に対して  C_s を始点に BFS を行うことで,任意の  1\leq s\lt t\leq K に対して  C_s,C_t を結ぶ最短経路長  w(s,t) が求まる.

あとは, 1\leq s\lt t\leq K に対して重み  w(s,t) の辺を張ったグラフに対して TSP を解けば OK.答える列の長さは 経路長 +1 となるのでそこだけ注意する. K 回の BFS と TSP を合わせると,全体の計算量は  O((N+M)K+2 ^ KK ^ 2)

実装量は少し多いが,すべて典型的な実装なのでさほど大変ではないと思う.

int n = sc.nextInt();
int m = sc.nextInt();
IntArrayList[] _g = new IntArrayList[n];
for (int i = 0; i < n; i++) {
    _g[i] = new IntArrayList();
}
for (int i = 0; i < m; i++) {
    int u = sc.nextInt() - 1;
    int v = sc.nextInt() - 1;
    _g[u].add(v);
    _g[v].add(u);
}
int[][] g = new int[n][];
for (int i = 0; i < n; i++) {
    g[i] = _g[i].toArray();
}
int k = sc.nextInt();
int[] c = sc.ints(k, e -> e - 1);

// BFS
int[][] ds = new int[k][n];
for (int i = 0; i < k; i++) {
    int[] d = ds[i];
    IntDeque dq = new IntDeque();
    Arrays.fill(d, -1);
    d[c[i]] = 0;
    dq.addLast(c[i]);
    while (dq.size() > 0) {
        int u = dq.removeFirst();
        for (int v : g[u]) {
            if (d[v] >= 0) continue;
            d[v] = d[u] + 1;
            dq.addLast(v);
        }
    }
}

// TSP
long[][] dp = new long[k][1 << k];
for (int i = 0; i < k; i++) {
    Arrays.fill(dp[i], Const.LINF);
    dp[i][1 << i] = 1;
}
int[][] bits = BitUtil.bits(k);
for (int s = 1; s < 1 << k; s++) {
    if (bits[s].length <= 1) continue;
    for (int v : bits[s]) {
        int p = s ^ (1 << v);
        for (int u : bits[p]) {
            if (ds[u][c[v]] < 0) continue;
            dp[v][s] = Math.min(dp[v][s], dp[u][p] + ds[u][c[v]]);
        }
    }
}
long ans = Const.LINF;
for (int i = 0; i < k; i++) {
    ans = Math.min(ans, dp[i][(1 << k) - 1]);
}
pw.println(ans >= Const.LINF ? -1 : ans);

F - Shift and Inversions

初めの一回は普通に転倒数を求める (これは有名かつ頻出なので理解した上でライブラリ化しておくとよさそう).先頭の要素を末尾に移動したときの転倒数の差分が分かればよい.

移動する要素を  X として, X より大きい要素は  N-1-X 個,小さい要素は  X 個存在するので,差分は  N-1-2X である.あとは,この差分を足しこむ操作を  N-1 回繰り返しながら出力すればよい.

全体の計算量は,初回の転倒数を求める部分が  O(N\log N) で,他は線形時間で動作するので  O(N\log N)

注意点といえば,転倒数は最大で  \dfrac{N(N-1)}{2} なので 32 bit 整数で計算するとオーバーフローする可能性があることくらい?

int n = sc.nextInt();
int[] a = sc.ints(n);
long inv = InversionNumber.solve(a);
pw.println(inv);
for (int i = 0; i < n - 1; i++) {
    inv += n - 1 - 2 * a[i];
    pw.println(inv);
}