ABC298 F問題 Rook Score

atcoder.jp

巨大なマス目から、(R,C) を選び、R行 または C列 のマス目の総和の問題です。
あらかじめ、各行の総和と各列の総和を計算しておくと、問題の値は、
 S = (R行の総和) + (C列の総和) - (R,C)マスの値
と求められます。
ほとんどのマス目は 0 であることから、0以外の値のある行と列のみをリストアップします。
行・列を総和が大きい順に並び変えて、 S の最大値を求めていきます。
列方向に探索するときに、 (R行の総和) + (C列の総和) の値がそれまでの最大値以下であれば、それ以降の列は調べる必要が無くスキップすることができます。
また、(R,C)マスの値をすぐに取り出せるように、(R,C)をキーとする辞書型としておきます。



コード例 (Julia)

function solve()

  # 入力
  readInts() =  parse.(Int,split(readline()))
  readInt()  =  parse(Int,readline())

  N = readInt()

  A = Dict()
  row = Dict()
  col = Dict()

  for k = 1:N
    r,c,x = readInts()
    A[ [r c] ] = x

    # r 行の和
    y = 0
    if haskey(row,r)
      y = row[r]
    end
    row[r] = x + y

    # c 列の和
    y = 0
    if haskey(col,c)
      y = col[c]
    end
    col[c] = x + y
  end

  nrow = length(row)
  ncol = length(col)

  # 和が大きい順に並び替え
  rows = sort(collect(row), by=x -> x[2], rev=true)
  cols = sort(collect(col), by=x -> x[2], rev=true)

  ans = 0

  for i = 1:nrow
    for j = 1: ncol
      v = rows[i].second + cols[j].second
      if ans >= v
        break
      end
      r = rows[i].first
      c = cols[j].first
      if haskey(A, [r c])
        v -= A[ [r c] ]
      end
      if v > ans
        ans = v
      end
    end
  end

  println(ans)

end  # function solve

# main
solve()

ABC298 E問題 Unfair Sugoroku

atcoder.jp

動的計画法で、サイコロを i 回振ったあとに地点j にいる確率を  dp(i,j) とします。
さらに1回サイコロを振ることによって地点 j+1, j+2, \cdots, j+P のいずれかに確率 \frac{1}{P}で到達します。(地点が N より大きくなる時は、N に置き換えます。)
このことから、
 dp(i+1, j+1) += \frac{1}{P} dp(i,j)
 dp(i+1, j+2) += \frac{1}{P} dp(i,j)

 dp(i+1, j+P) += \frac{1}{P} dp(i,j)
と確率の加算を行います。これをループ処理することで、 i 回後にゴールに到達している確率が求められます。

高橋君と青木君について、この確率を求めます。
この確率は累積確率なので、その差分が高橋君が i 回目に初めてゴールに到達する確率になります。
このとき、青木君がゴールに到達していない確率は 1 から累積確率を引いた値で、これらの確率の積で高橋君が i回目にゴールに到達したときに高橋君が勝つ確率が求められます。
これを i についてループすることで高橋君が勝つ確率が求められます。


コード例 (Julia)

_MOD = 998244353

function solve()

  # 入力
  readInts() =  parse.(Int,split(readline()))
  readInt()  =  parse(Int,readline())

  N, A, B, P, Q = readInts()

  p = invmod(P,_MOD)  # 1/P
  q = invmod(Q,_MOD)  # 1/Q

  taka = zeros(Int,N+1,N)
  aoki = zeros(Int,N+1,N)

  taka[1, A] = 1
  aoki[1, B] = 1

  # 高橋君の DP
  for i = 2:N
    for j = 1:N
      if taka[i-1, j] == 0
        continue
      end
      for k = 1 : P
        n = min(j+k, N)
        taka[i, n] += taka[i-1, j] * p
        taka[i, n] %= _MOD
      end
    end
  end

  # 青木君の DP
  for i = 2:N
    for j = 1:N
      if aoki[i-1, j] == 0
        continue
      end
      for k = 1 : Q
        n = min(j+k, N)
        aoki[i, n] += aoki[i-1, j] * q
        aoki[i, n] %= _MOD
      end
    end
  end

  ans = 0

  for i = 2:N
    prob = taka[i, N] - taka[i-1, N]  # 高橋君が i 回目にゴールに着く確率
    pa = 1 - aoki[i-1, N]             # 青木君がゴールに着いていない確率
    ans += (prob * pa) % _MOD
    ans %= _MOD
  end

  if ans < 0
    ans += _MOD
  end

  println(ans)

end  # function solve

# main
solve()

ABC298 D問題 Writing a Numeral

atcoder.jp

各クエリで S を数とみなした値を更新していきます。
クエリ1は、値を10倍して xを加えます。
クエリ2は、先頭の数字に 10^k (k=桁数-1)をかけた値を引きます。
このクエリのために、追加する数字を配列に記録することと、 10^k (k=1,2,\cdots)をあらかじめ計算しておきます。
それぞれの値は、mod 998244353 での値とします。
クエリ3は、更新している値をそのまま出力するだけです。


コード例 (Julia)

_MOD = 998244353

function solve()

  # 入力
  readInts() =  parse.(Int,split(readline()))
  readInt()  =  parse(Int,readline())

  Q = readInt()

  ord = zeros(Int,600001)   # 10^N
  ord[1] = 1
  for i = 1:600000
    ord[i+1] = (ord[i] * 10) % _MOD
  end

  S = zeros(Int,600001)
  S[1] = 1
  ans = 1
  head = 1
  tail = 2

  for q = 1:Q
    A = readInts()
    if A[1] == 1
      x = A[2]
      ans = (ans * 10 + x) % _MOD
      S[tail] = x
      tail += 1
    elseif A[1] == 2
      n = tail - head
      a = (ord[n] * S[head]) % _MOD
      ans -= a
      if ans < 0
        ans += _MOD
      end
      head += 1
    elseif A[1] == 3
      println(ans)
    end
  end

end  # function solve

# main
solve()

ABC297 F問題 Minimum Bounding Box 2

atcoder.jp

期待値の求め方

マスの数は H \times W個で、ここから K 個 の選び方は、 {}_{H \times W} \mathrm{C}_{K} 通りです。
選んだマスの位置を \left(x_1,y_1\right), \left(x_2,y_2\right), \cdots , \left(x_K,y_K\right) とすると、スコアは \left(\max\left(x_i\right) - \min\left(x_i\right) + 1 \right) \times \left(\max\left(y_i\right) - \min\left(y_i\right) + 1 \right) となります。

スコアが h \times w になる選び方が N_{h,w} 通りあるとすると、スコアの期待値は
 \displaystyle \frac{1}{{}_{H \times W} \mathrm{C}_{K}}\sum_{h=1}^{H} \sum_{w=1}^{W} \left(h \times w \times N_{h,w} \right)
と計算されます。ここから  N_{h,w} を求めることを考えていきます。

場合の数の計算

 h マス、横 wマスの固定範囲内で  K マスの選び方は  {}_{h \times w} \mathrm{C}_{K} 通りですが、これにはスコアが h \times w より小さな選び方も含んでいます。
そこで外側の4辺について、辺上に選んだマスがある場合(〇)とひとつも無い場合(×)で分類します。これによってマスの選び方を 2^4 = 16種類に分類できます。

求めたいのは、①の場合の数です。

選び方全体
 {}_{h \times w} \mathrm{C}_{K} = ①+②+③+④+⑤+⑥+⑦+⑧+⑨+⑩+⑪+⑫+⑬+⑭+⑮+⑯

辺A のマスを選ばない選び方
 {}_{(h-1) \times w} \mathrm{C}_{K} = ⑨+⑩+⑪+⑫+⑬+⑭+⑮+⑯

辺C のマスを選ばない選び方
 {}_{(h-1) \times w} \mathrm{C}_{K} = ③+④+⑦+⑧+⑪+⑫+⑮+⑯

辺B のマスを選ばない選び方
 {}_{h \times (w-1)} \mathrm{C}_{K} = ⑤+⑥+⑦+⑧+⑬+⑭+⑮+⑯

辺D のマスを選ばない選び方
 {}_{h \times (w-1)} \mathrm{C}_{K} = ②+④+⑥+⑧+⑩+⑫+⑭+⑯

辺A,B のマスを選ばない選び方
 {}_{(h-1) \times (w-1)} \mathrm{C}_{K} = ⑬+⑭+⑮+⑯

辺B,C のマスを選ばない選び方
 {}_{(h-1) \times (w-1)} \mathrm{C}_{K} = ⑦+⑧+⑮+⑯

辺C,D のマスを選ばない選び方
 {}_{(h-1) \times (w-1)} \mathrm{C}_{K} = ④+⑧+⑫+⑯

辺D,A のマスを選ばない選び方
 {}_{(h-1) \times (w-1)} \mathrm{C}_{K} = ⑩+⑫+⑭+⑯

辺A,C のマスを選ばない選び方
 {}_{(h-2) \times w} \mathrm{C}_{K} = ⑪+⑫+⑮+⑯

辺B,D のマスを選ばない選び方
 {}_{h \times (w-2)} \mathrm{C}_{K} = ⑥+⑧+⑭+⑯

辺A,B,C のマスを選ばない選び方
 {}_{(h-2) \times (w-1)} \mathrm{C}_{K} = ⑮+⑯

辺A,D,C のマスを選ばない選び方
 {}_{(h-2) \times (w-1)} \mathrm{C}_{K} = ⑫+⑯

辺A,B,D のマスを選ばない選び方
 {}_{(h-1) \times (w-2)} \mathrm{C}_{K} = ⑭+⑯

辺C,B,D のマスを選ばない選び方
 {}_{(h-1) \times (w-2)} \mathrm{C}_{K} = ⑧+⑯

4辺すべてのマスを選ばない選び方
 {}_{(h-2) \times (w-2)} \mathrm{C}_{K} = ⑯


以上の16式から16個の変数を求めると、 {}_{p \times q} \mathrm{C}_{K} = F(p,q) と表示することにして
 ⑯ = F(h-2,w-2)
 ⑧=⑭=F(h-1,w-2)-F(h-2,w-2)
 ⑫=⑮=F(h-2,w-1)-F(h-2,w-2)
 ④=⑦=⑩=⑬= F(h-1,w-1)-F(h-1,w-2)-F(h-2,w-1)+F(h-2,w-2)
 ⑥= F(h,w-2)-2F(h-1,w-2)+F(h-2,w-2)
 ⑪= F(h-2,w)-2F(h-2,w-1)+F(h-2,w-2)
 ②=⑤=F(h,w-1)-2F(h-1,w-1)-F(h,w-2)+2F(h-1,w-2)+F(h-2,w-1)-F(h-2,w-2)
 ③=⑨=F(h-1,w)-2F(h-1,w-1)-F(h-2,w)+2F(h-2,w-1)+F(h-1,w-2)-F(h-2,w-2)
 ①= F(h,w)-2F(h-1,w)-2F(h,w-1)+4F(h-1,w-1)+F(h,w-2)+F(h-2,w)-2F(h-1,w-2)-2F(h-2,w-1)+F(h-2,w-2)

 h マス、横 wマスの範囲の選び方は  \left(H-h+1\right) \times \left(W-w+1\right) 通りあるため、結局、
 N_{h,w}=\left(H-h+1\right) \times \left(W-w+1\right) \times ①
となります。

コード例 (Julia)

const MOD_ = 998244353

const NMAX = 1_000_000

fact    = Vector{Int}(undef, NMAX)
invfact = Vector{Int}(undef, NMAX)

function setup_fact( )
  fact[1] = 1
  invfact[1] = 1
  for i = 2:NMAX
    fact[i] = (i * fact[i-1]) % MOD_    # N!
    invfact[i] = invmod(fact[i],MOD_)   # 1/N!
  end
end

function Comb( N, M )
  if N < 0 || M < 0 || N - M < 0
    return 0
  end
  if M == 0 || N - M == 0
    return 1
  end

  ret = (invfact[M] * invfact[N-M])%MOD_
  ret *= fact[N]
  return ret % MOD_
end

function solve()


  # あらかじめ n!, 1/n! を計算
  setup_fact()

  # 入力
  readInts() =  parse.(Int,split(readline()))
  readInt()  =  parse(Int,readline())

  H,W,K = readInts()

  F = zeros(Int, H, W)

  for h=1:H, w=1:W
    F[h,w] = Comb( h*w, K )
  end

  ans = 0

  for h=1:H, w=1:W
    maru1 = F[h,w]
    maru1 += (h>1) ? -2*F[h-1,w] : 0
    maru1 += (w>1) ? -2*F[h,w-1] : 0
    maru1 += (h>1&&w>1) ? 4*F[h-1,w-1] : 0
    maru1 += (w>2) ?   +F[h,w-2] : 0
    maru1 += (h>2) ?   +F[h-2,w] : 0
    maru1 += (h>1&&w>2) ? -2*F[h-1,w-2] : 0
    maru1 += (h>2&&w>1) ? -2*F[h-2,w-1] : 0
    maru1 += (h>2&&w>2) ?   +F[h-2,w-2] : 0
    maru1 %= MOD_
    if maru1 < 0
      maru1 += MOD_
    end
    coef = h * w * (H-h+1) * (W-w+1)
    coef %= MOD_
    ans += coef * maru1
    ans %= MOD_
    if ans < 0
      ans += MOD_
    end
  end

  ans *= invmod(F[H,W], MOD_)
  ans %= MOD_

  println(ans)

end  # function solve

# main
solve()

ABC297 E問題 Kth Takoyaki Set

atcoder.jp

安いほうから順に  K 個を選ぶことを考えてみます。
各種類を1つだけ買った時の価格 A_iを集合 Sに加えます。
集合 Sの中から一番安い価格を取り出します。
その価格 cに、さらに1つ追加で買うときの価格 c+A_i Sに加えます。
 S は同じ値の重複を許さないようにして、 Sから一番安い価格を取り出し、一つ追加した価格を加えることを繰り返したとき、 K回目に取り出した価格が求める答えです。


コード例 (Julia)

using DataStructures

function solve()

  # 入力
  readInts() =  parse.(Int,split(readline()))
  readInt()  =  parse(Int,readline())

  N,K = readInts()
  A = readInts()

  s = SortedSet{Int}()

  for i = 1:N
    push!(s,A[i])
  end

  for i = 1:K-1
    c = pop!(s)
    for j = 1:N
      push!(s,c+A[j])
    end
  end

  println(pop!(s))

end  # function solve

# main
solve()

ABC297 D問題 Count Subtractions

atcoder.jp

正の整数 A,Bに対して
 A > B なら A \to A - B
 A < B なら B \to B - A
を何回繰り返すと A = B になるかという問題です。

この操作を1回ずつ行うと、たとえば A = 1, B = 10^{18}のとき 10^{18}回程度になるため時間がかかりすぎます。

そこで、 A > B の場合、 A B で割った商 dと余り rを求めます。 d 回の操作で、 A \to A - d B = r になるので、
 r\ne 0 であれば次に r B (r < B) に対する操作を考えます。 r=0であれば、d-1 回の操作後に A-(d-1)B = Bと等しくなります。


コード例 (Julia)

function solve()

  # 入力
  readInts() =  parse.(Int,split(readline()))
  readInt()  =  parse(Int,readline())

  A,B = readInts()

  if A < B
    A,B = B,A
  end

  ans = 0

  while A > B
    d,r = divrem(A,B)
    if r==0
      ans += d-1
      A -= (d-1)*B
    else
      ans += d
      A,B = B,A-d*B
    end
  end

  println(ans)

end  # function solve

# main
solve()

ABC296 F問題 Simultaneous Swap

atcoder.jp

条件の整理

並び替えで A B が一致するためには、少なくとも  A B で値の出現回数が一致する必要があります。
一致しない場合は、「No」です。

操作前に、 (A_i, B_i), (A_j,B_j),(A_k,B_k) の各組が同じ位置のとき、操作後には (A_j, B_k), (A_i,B_j),(A_k,B_i) の組み合わせになります。
並び順は問題にしていないので、 A の方を元の位置に戻すと、 (A_i,B_j),(A_j, B_k), (A_k,B_i) となり、これは Bの方に (i,j,k) の巡回置換を行ったことに相当します。

以上から、 B に長さ3の巡回置換を繰り返すことで A と一致させることができるか、という問題に言い換えることができます。

長さ3の巡回置換は、2回の互換(2要素の入れ替え)に分解することができるので、 B Aに一致させるための互換の回数が奇数のときは、長さ3の巡回置換だけでは一致させることができません。
逆に偶数回の互換で一致させることができる場合、長さ3の巡回置換を繰り返しで一致させることができます。

また、 B の中に同じ値がある ( B_i = B_j (i\ne j)) 場合、互換は偶数回にも奇数回にもなるので、一致させることができます。


コード例 (Julia)

function solve()

  # 入力
  readInts() =  parse.(Int,split(readline()))
  readInt()  =  parse(Int,readline())

  N = readInt()
  A = readInts()
  B = readInts()

  # A と B の中身が一致しているか調べる

  n1 = zeros(Int,N)
  n2 = zeros(Int,N)

  for i = 1:N
    n1[A[i]] += 1
    n2[B[i]] += 1
  end

  for i = 1:N
    if n1[i] != n2[i]
      # i の登場回数が A と B で異なる
      #   --> 並び替えても一致しない
      println("No")
      return
    end
  end

  for i = 1:N
    if n1[i] >= 2
      # 同じ数字が2回以上現れる
      #   --> 一致させることができる
      println("Yes")
      return
    end
  end

  # A を小さい順に並び替えたときの B の並び
  C = zeros(Int,N)
  for i = 1:N
    C[A[i]] = B[i]
  end

  n_swap = 0

  i = 1
  while i <= N
    p = C[i]
    if p == i
      i += 1
    else
      n_swap += 1
      C[i] = C[p]
      C[p] = p
    end
  end

  if n_swap % 2 == 0
    println("Yes")
  else
    println("No")
  end

end  # function solve

# main
solve()