HHKB プログラミングコンテスト 2020 D - Squares
2年前の問題になりますが HHKB プログラミングコンテスト 2020のD - Squaresについてです。
atcoder.jp
問題としては1辺Nの正方形のグリッド中にA(1辺a),B(1辺b)の2つの正方形を重ならないような配置の総数を求めるというもの。
解説だと余事象から求めている(全体からA,Bが重なる方法を引く)のだけれど、別解で解いたので書いておきます。
包除原理で重ならない方法を直接的に計算する方法です。
2つの正方形が重ならないためには、x軸もしくはy軸に並行に引いた線でA、Bの正方形を分離できる必要があります。
上下に分けることができる配置を、左右に分けることができる配置を
とすると、
が答えです。
はA,Bが対角の位置となるように十字に線を引くことができる配置です。
上下にABの順に並ぶものを、同様に左右にABの順に並ぶものを
とします。
対称性から、以下が成り立ちます。
ということで最終的に以下の値を求めます。
Bの左端をx,上端をyとするとはそれぞれ以下の式で求めることができます。
展開して整理すると以下のようになります。展開は Wolfram|Alpha: Computational Intelligence を使いました。
とするとすっきりします。
Microsoft Certified: Azure Fundamentals 取得した
5/12,13で予定していた Azure Virtual Training の 2日目 がキャンセルとなり、丸1日分無駄になるというアクシデントもありましたが、AZ-900 の認定試験に合格し、Azure Fundamentals 取得しました。
Foundamental なので初級者向けですが、開発者知識だとうっすらとしか知らないことがおおくて勉強になりました。
あとは最近のオンラインの勉強環境にしっかり触れることができて体験を結構アップデートできました。
埋め込んでいるcredlyもOpen Badge対応。UdemyでAZ-900の過去問もやりました。
ABC 246 E - Bishop 2
解説では拡張ダイクストラや01BFSだけど、ずっとTLEで通すまで時間がかかったので忘れないようメモ。
右上がりのラインと左上がりのラインで到達できる範囲を頂点と考える。 すると各マスの右上がりラインと左上がりラインに辺を貼ることで、曲がるときにコスト1と考えることができ、普通のBFSの最短経路で求めることができる。 ただ始点/終点はそれぞれ2つあるので、簡便のため、始点/終点の頂点を作成し、それぞれのマスのラインの頂点に辺をはる、この時結果としては+1になるのに注意。
最初に辺をまとめるところはN * N * 2の頂点を用意して一旦UnionFindでまとめてからグラフを構築するか、 グラフに頂点を足せるようにしておいて、左上、右上のマスと連結できないときにあたらしく頂点を増やせばよい。
class Graph def initialize; @g = []; end def add_node; @g << []; @g.size - 1; end def add_edge(a,b); ... ; end def bfs(s,t); ... ; end end
提出 : atcoder.jp
AtCoder ABC 167 F Bracket Sequencing
ABC 167は5完 Eの実装で時間を溶かしてしまってFが解けなかった。
問題としては( と )だけの文字列が複数与えられるので、好きな順番で連結して、括弧の対応が取れた文字列にできるかどうかの判定
それぞれの文字に対し対応する括弧を潰すと、))(((のような形になるので、)の数をr, (の数をlとして、(r, l)で考える。
先頭にできるのはr = 0だけ、末尾にできるのはl = 0だけ、それ以外は中間部
カッコ列は(の数が先行して、)が追い付く形になるので、i個連結して括弧の対応が取れた状態にするには 、ただし
を満たす順番で、 且つ
である必要がある。
先頭部、中間部(r - lで昇順にソート)、後尾部を順次連結して、全部追加できるか + 括弧の数が同じかを確認して終了。
中間部(r - lで昇順にソート)で貪欲にやって通ったけど不安。解説見るとr - l <= 0, r - l > 0に分けてr 昇順, l降順で足していくとよいらしい。なるほどなー。
N = gets.to_i S = [] T = [] W = [] N.times do w = gets.chomp r = 0; l = 0; (0 ... w.size).each do |i| c = w[i] if c == '(' l += 1 elsif l > 0 l -= 1 else r += 1 end end if l.zero? # terminate T << [r, l] elsif r.zero? S << [r, l] # start else W << [r, l] end end l_sum = S.inject(0){|s,(r,l)| s + l } W.sort_by!{|r,l| r - l } W.each do |(r,l)| if l_sum >= r l_sum = l_sum - r + l else puts 'No' exit end end T.each do |(r,l)| if l_sum >= r l_sum = l_sum - r + l else puts 'No' exit end end puts l_sum.zero? ? 'Yes' : 'No'
簡易 PQueue
バイナリヒープの優先度付きキュー
使い方
pq = PQueue.new %w/1 12 123 abcd abc/.each do |w| pq.enq(w,w.size) # 優先度を指定して追加する。#enq(value, priority) end puts pq.deq until pq.empty? # #deqでpriorityの小さいもの取り出せる
1 12 abc 123 abcd
class PQueue Node = Struct.new(:value, :priority) def initialize @heap = [] end def size; @heap.size; end def empty?; @heap.empty?; end def peek; empty? ? nil : @heap[0].value; end def enq(value, priority) el = Node.new(value, priority) j = @heap.size @heap << nil while j > 0 up = (j - 1) / 2 break if @heap[up].priority < el.priority @heap[j] = @heap[up] j = up end @heap[j] = el self end def deq return nil if empty? return @heap.shift.value if size == 1 ret = @heap[0] @heap[0] = nil el = @heap.pop i = 0 while 2 * i + 1 < @heap.size l = 2 * i + 1 r = 2 * i + 2 j = (r == @heap.size || @heap[l].priority <= @heap[r].priority) ? l : r break if el.priority <= @heap[j].priority @heap[i] = @heap[j] i = j end @heap[i] = el return ret.value end end
ABC-147 E
解説通りのO(HW(H + W)M) をRubyで書くとTLEした(コンテストは不参加)のでbetrue12さんのを参考に読み解いてみたら、震えた。
取りうる差分をビットで表して、右ビットシフト、左ビットシフトでORを取るDPしてた。すげぇ。
下記は理解後、自分で書いてみたコード
H,W = gets.split.map(&:to_i) M = (H + W) * 80 A = H.times.map { gets.split.map(&:to_i) } B = H.times.map { gets.split.map(&:to_i) } dp = Array.new(H){ Array.new(W, 0)} dp[0][0] = 1 << M W.times do |x| H.times do |y| d = (A[y][x] - B[y][x]).abs dp[y][x] = (dp[y][x] << d | dp[y][x] >> d) if x == 0 && y == 0 dp[y][x] |= (dp[y][x - 1] << d | dp[y][x - 1] >> d) if x > 0 dp[y][x] |= (dp[y - 1][x] << d | dp[y - 1][x] >> d) if y > 0 end end G = dp[H - 1][W - 1] puts M.times.find{|i| G[M - i] > 0 || G[M + i] > 0 }
ABC146-F
時間内にたどり着けなかったけど、たぶん後ろから貪欲にとれば、辞書順最小だな。
j回でたどり着けるとして、j回目の出目をとすると、
なので、辞書順最小は後ろからとれるだけとるのが最適なはず。
一度、後ろから累積minを取ってやると楽ちん。
def max(a,b); a > b ? a : b; end N, M= gets.split.map(&:to_i) S = gets.chomp smin = Array.new(N + 1, N) (0 .. N).reverse_each.inject(N) do |m,i| if i + M < m puts -1 exit end smin[i] = S[i] == '0' ? i : m end ANS = [] N.times.inject(N) do |pos,i| break if pos.zero? smin[max(pos - M, 0)].tap do |j| ANS << pos - j end end puts ANS.reverse * ' '