塩見周子の徒然日記

自分のことを塩見周子と思い込んでいるオタクです

3/3 AtCoder(グリッドの扱い)/ウニ(G線上のアリア)

こんにちは。金恋GTのグランドエンディングまで見て涙したオタクです。いやーよかった.........................................................................................................................................................................................................................................................................................................................................................。 

 

AtCoder

解くに至った問題(7問)

ABC:042B/075B,C/120A,B,C

AGC:007A

 

ABC075B

グリッドを操作する苦手なタイプの問題。放置しまくってました。

結局、難しいところは「周囲八方向が'.'か'#'かを確認する際の例外処理」に集約されると思われる 

例えばグリッドの外周であれば多くても五か所(角に至っては三か所)だけを調べればよいが、周りにほかのマスがある場合は八か所全部調べないといけない、といったことを、いちいち場合分けして処理するのが面倒くさいというところを解消したい

で、そういう時に「あらかじめ移動する方向の差分のリストを持っておく」というのが便利である

例えば、

dx = [0,0,1,1,1,-1,-1,-1]
dy = [1,-1,1,-1,0,1,-1,0]

としておけば、(dx[k],dy[k])をそれぞれx方向、y方向の移動と捉えることで、8通りすべて賄うことができる

さらに、例外処理は面倒なので、全八通りを出したうえで、

①(調べるマスの行番号)+dy[k]が0未満、もしくはH以上であれば調べない

②(調べるマスの列番号)+dx[k]が0未満、もしくはW以上であれば調べない

とすれば、実質的に例外処理をしたことと同じになる

全方位を探索するのに、こういう工夫ができると楽

 

また、昨日もちらっと書いたが、例えば

L = [['A','B','C'],['D','E','F'],['G','H','I']]

というリストを

ABC

DEF

GHI

と出力したい場合は、

for i in range(3):

    for j in range(3):

        print(L[i][j],end = '')   #行ごとに改行なしで出力

    print()           #一行出力しきったら改行

とすればよい

 

atcoder.jp

 

AGC007A

与えられた模様が右、下の移動のみで進んでいけるかどうかを判定する問題

右に行けば列番号が1、下に行けば行番号が1増えることから、結局(1,1)から(H,W)に移動するまでの間にH+W-2回の移動する必要がある

(1,1)にも模様'#'がついていることを考えれば、全体で'#'の数を合計するとH+W-1になるので、これが与えられた情報と合致するかを判定すればよい

 

atcoder.jp

 

 

ABC042B

 

文字列を辞書式に並べてつなげて出力する問題

文字列の比較は不等号でできて、小さいほうが辞書で先に出てくる方になる(それはそう)

 

atcoder.jp

 

 

解けなかった問題

ABC:120D

ABC120D

 

見た瞬間「UF木やんけ!」と思ったものの与えられる橋の情報が多く、それだけで逐一確認していくのは不可能だと判断

次に「あれ、逆順から島をつなげていって、行き来できる島を数えていけばよくね?」と考えたものの実装できず

サイズ付きのUF木なるものがあるらしい これはrank(木の根っこ)の情報に加えて、各要素が所属するグループの全要素数の情報を付与したもの

これがあれば、

①島同士が最初から同じグループに所属していた→島をつなぐ作業により新たに行き来できる島はないため+0

②島同士が別グループに所属していた→島をつなぐ作業によって新たに行き来できる島は(今連結した島を含め)その島同士が所属するグループの全要素数に等しいので、答えにグループの要素数の積を足す、そして、そのグループ同士をuniteでくっつける

結局これをM回繰り返せばよくなり、時間的には間に合う

明日実装してみます サイズ付きUF木を学習する

 

 

 

 

ウニ

 

アリア鳥取れました  うれしい

f:id:saguh:20190304015223j:plain

 

Warcry地帯は擦ったら全ピカしました  最近擦りに自信がつき始めています

この曲、とにかく巻き込みやすいから大変だし、それだけじゃなくてラストのフリックも地味に抜けやすくてなかなか手こずる奴でしたが鳥取れて感無量でした

そろそろ15.5も見えてきそうなので、上位の曲を練習していきたいです

3/2 AtCoder(マス目を扱う(圧縮、反転)、最短経路、配るループ、10進法からn進法への変換(0埋めのzfill))

こんにちは。一時に寝たのに起きたら十二時になっていて自分の体のポンコツさを実感しています。とーです。

 

AtCoder

解くに至った問題(10問)

CADDi 2018 for Beginners:B

全国統一プログラミング王決定戦 エキシビション:B

CODE FESTIVAL 2017 qual A:B

CODE FESTIVAL 2017 qual C:B

AGC:017A

ABC:021B/026C/104C/107B/119C

 

 

 

CODE FESTIVAL 2017 qual A B

N行M列のオセロみたいに、石が全部白を表にしておいてあり、白黒を反転させることができる 黒の数を与えられた数と等しくできますか?という問題

行をひっくり返すことをk回、列をひっくり返すことをl回やったとすると、黒が表を向いている石の数は

k*(M-l)+l*(N-k)

となる

なぜなら、k*(M-l)はk回分の行のひっくり返しをしたうえで(この時ひっくり返した行はすべて黒)、l回のひっくり返しをしていない列を選ぶ(ひっくり返した列を選ぶとまた白に戻ってしまう)場合の数と等しいからである(l*(N-k)も同じ)

んで、これが与えられた数と等しくなるかを全探索すればよい(N,K<=1000なので間に合う)

 

 

ABC107B

Grid Compression

列や行を圧縮できるところまで圧縮しましょうねという問題

愚直に列や行を取り除いて圧縮するアルゴリズムを考えていたけど、ちょっとそれは厳しいかなーと思い答えを見たところ、

「'#'が含まれる行、列をマークしておき、その行と列が交差する部分に存在する要素のみが残る」

とのこと

実際それはそうという感じで、マークがつかない→列or行がすべて'.'で構成されているため当然取り除かれるし、交差しているところは行で取り除く作業にも列で取り除く作業にも引っかからないから取り除かれない

 

考え方はこれでよいので、あとは実装を頑張る

行と列の数だけFalseで埋めたリストを持っておく

 

row = [False]*H

col = [False]*W

 

リストL[i][j]が'#'であれば、row[i] = True, col[j] = Trueとしてあげれば、その部分は交差しているのでOK(つまり、i行目とj列目は取り除かれない)

それが終わったら、今度はTrueになっている各行に対し、その行の中で列リストがTrueに代わっている成分だけをprintするようにしてあげればよい

列の改行はいらないが、行ごとの改行はいるので、そこに注意しながら出力する

(改行がいらないときはprint(なんちゃら,end = '' )とすればよいし、改行がいるときはprint()と下に添えればよい)

True/Falseで管理する便利さをまた一つ実感した

atcoder.jp

 

 

ABC021B

最短経路か否かを判定する問題

重みの無い経路における最短経路は閉路(同じところを二度以上通ること)がない(だから、現れる頂点はユニーク)ということに注意すればよい

 

ABC026C

木構造のようになった上司、部下の関係から、トップの人の給料を求めようという趣旨の問題

あれ?UF使えそう?とか思ったけど、木構造かつその構造が下にどんどん広がっていってよいということから、UFとはちょい違う実装をしなければいけない

やってみたがちょっと厳しそうだったので断念 解説ACをしました

肝心なところは、「自分の社員番号よりも社員番号の小さい人しか上司になれない」ということ

だから、番号を逆順に辿っていくことで、最終的にトップの人が求まればよい

 

考え方は以下の通り

まず、i番目に社員番号i+1番の人の給料、直属の部下を入れるリストPay_Subを作る

例えば(トップ含め)7人であれば、

Pay_Sub == [[1,],[1,],[1,],[1,],[1,],[1,],[1,]]

みたいな感じ

そして、部下の情報を入れていく

入力が

1

1

2

2

3

3

みたいな感じであれば、

Pay_Sub == 1,[1,2,[1,[3,4]],[1,[5,6]],[1,],[1,],[1,],[1,[]]]

のように入る

そして、リストの中を逆順に見ていき、

len(Pay_Sub[i][1]) != 0のとき(つまり部下がいる場合)、

新たに作ったリストに部下の給料を入れていき、そのmax,minを調べてPay_Sub[i][0](社員番号i+1番の人の給料)を更新、というのを、iがN-1から0までやればよい

そして、最終的にPay_Sub[0][0]が答えとなる

 

atcoder.jp

 

ABC119C

本番中にACできなかった問題

竹がN本あれば、一本の竹につき、Aに使う、Bに使う、Cに使う、使わないの4パターンがあり、それを全探索すればよい N<=8なので多くても65536通りになり間に合う

また、竹をつなげてから伸ばしたり縮めたりすることと、一本一本の竹を伸ばしたり縮めたりしてからそれらの竹をつなげる作業は等価なので、今回は簡単な「全部繋げてから差分を伸ばす、縮める」でやるのがよさそう 

竹をどこに使うかの全探索のやり方が分からず(解説には再帰を使ってやる手法が書いてあったけどこれが思いつくほど精進を積んでない)、0から4**Nを4進法で表し、それぞれの桁を竹一本一本に割り当て、(無から有を生やすことは出来ないので、最低一つの竹をA,B,Cに使うことに注意)0だったら使わない、1だったら竹Aに使う......みたいな感じでやり、このやり方でも一応ACできたが、4進法に直すところで明らかに時間を食っており、改善が必要

誰か教えてください

atcoder.jp

 

 

一応10進法をn進法に変換する簡単なやり方があったのでそれを載せておく

①10進法をn進法に直し、文字列として出力

X = int(input())
def Base_10_to_n(X, n):
    if (int(X/n)):
        return Base_10_to_n(int(X/n), n)+str(X%n)
    return str(X%n)

 

②10進法をn進法に直し、左から規定の桁まで0埋めしたのを出力

X = int(input())
def Base_10_to_n(X, n):
    if (int(X/n)):
        return Base_10_to_n(int(X/n), n)+str(X%n)
    return str(X%n)

print(Base_10_to_n(X, n).zfill(P))        #P桁埋める zfillは文字列のみ有効

 

 

参考にさせていただいたのはこちらのページです。ありがとうございます。

Python で10進数とn進数の変換 

Python Tips:Python でゼロパディングしたい - Life with Python

 

ABC104C

これも実質的に全探索の問題 工夫が必要で、問題を①全部解く②ちょっと解く③一問も解かない、の三つに分類して、そのうえで「ちょっと解く」は0か1種類でよい(二種類以上を解くのであれば、点の高い方を解いた方が得だから)ということを念頭に置いて解く 全探索は上で紹介した二進法表記でイケるらしい(想定解がそれだから全探索するときN進法に置き換えるのはあながち間違いではないか......?)

atcoder.jp

3/1 AtCoder(一個飛ばしのDP)

こんにちは。minori解散の一報を受け悲しみに暮れています。とーです。

悲しみのあまり金恋をぶっ通しでやり続けてしまいました。明日からはちゃんとした生活習慣に戻ります。

 

AtCoder

解くに至った問題(1問)

DPまとめコン:C

 

DPまとめコンC

解説がないので一応覚書として

DPを使うのは(題名からして)明らかなので、とりあえずどういうDPを作ればよいかなと考えたところ、i日目に活動aをやった時の幸福度の最大値は、i-1日目に活動b、活動cのいずれかをやった時の幸福度の最大値にi日目の活動aの幸福度を足したものになると考えれば

dp_a = [i番目には、i+1日目にaをやった時の幸福度の最大値]

というリストをdp_b,dp_cと作ってあげて(漸化式から簡単に作れる)、それぞれのN番目の要素を比較し、最大をとればよい

計算はO(N)だけかかるので、N<=10**5なら十分間に合う

atcoder.jp

 

 

2/28 AtCoder(DPと見せかけて全探索、しゃくとり法(部分列の積、和)、ゲームの必勝法(grundy数))

こんにちは。昼過ぎに起きて洗濯や自炊をしていたらもう7時になって愕然としていました。とーです。

 

AtCoder

解くに至った問題(5問)

ABC:032B,C/054B/060D/112C

 

ABC060D

ナップザック問題やんけ!できるかも!と思ってやってみたところ、重さdpしか知らないため制限重量W <= 10**9ではとてもじゃないけど扱えないということで答えを見ました 

 

この問題のキモは、与えられた品物の重量の最大値と最小値の差が高々3しかないということ つまり、最初に重さごとに分類しておいて、それから「重さxの品物をいくつ取るか」ということを全探索で求めていけば十分間に合う(最悪の計算量は、100個の品物が25個ずつに均等に分かれて、品物をいくつ取るかの選択が25**4となるときだが、これは10**6よりは小さいのでなんとか間に合う)

 

pythonは実行時間がやや遅いので、ここでは工夫して、まず品物を重さごとに分類した後で価値の高い順にソート→前から累積和をとっていく(つまりリストのi番目が、そのリストの中からi+1個選んだことに相当する)ようにした これによって、品物をいくつ取るかを決めた時点で、その個数選ぶ際の最大価値が一目で分かるようになった

 

しかし、初めから特定の重さの品物がないときは、例外処理の全探索のコードを書くのが難しくなるので、品物を0個選ぶのと、価値0の品物を一つ選ぶことは等価と考え、ソートして累積和をとった後でリストのケツに0を突っ込む作業をした こうすることで、例外処理をする必要がなくなる

 

atcoder.jp

 

ABC032C

 

数列の中にある部分列の項の積を、与えられた数字Kよりも小さくなるようにしながら、できるだけ長く伸ばすと長さはどんなもんよ?っていう内容

しゃくとり法というやり方が使えます(蟻本にはp.137あたりに、部分列の項の和を求めるところでこの手法を使っていますが、今回は積で使っています)

 

イメージとしては、

①スタートとゴールを持っておく

②スタートからゴールまでの積がK以下であればゴールを延ばす(そのうえでゴールの数字を掛ける)

③スタートからゴールまでの積がKより大きくなったらスタートを縮める(そのうえでスタートの数字で割る)

というのを繰り返しやっていく

 

結構簡単そうに見えるが実装の際に気を付けなければならないことも多く、メジャーなものとしては

①スタートがゴールを追い越さないようにする

②ゴールが数列のケツにたどり着いた時点でゴールを延ばすのをやめる

みたいなのがある

 

また、足し算の実装は比較的簡単だが、掛け算になるとこれまた気を付けるべき点増えて、この問題に限って言えば、

①数列の中に0が入っていたら、Kがどんな数字であれ条件は満たされる

②K == 0である場合、数列の中に0があればオッケーだけど、逆に0がない場合は条件を満たすような部分列はない(これはコーナーケースでひっかかりがち)

③例えば数列が 2 2 2 2 でK == 16なら、掛け算が一度も止まることなく(始点と終点を動かすことなく)最後まで行ってしまうのでちゃんとカウントを最後でとめる

といった点に注意すべきである

 

atcoder.jp

とりあえずpython3でのコードを書いたが、これもっと改善できると思うので、次回解くときには簡潔に書く工夫ができたらいいな

 

解けなかった問題

ABC:059D

 

ABC059D

grundy数というのを使う

grundy数というのは

①その状態からどの状態へも遷移できない場合を0

②遷移できる先があれば、その遷移先のgrundy数以外の最小の非負整数をとる

という数

簡単な例で言えば、爆弾ゲームというのがある

小学校で、「0から始め、交互に1または2を足して、最終的に11を言った人の勝ち」というゲームをしたことがある

結論から言うと、これは先手必勝のゲームである これをgrundy数を使って考えてみる

 

これにgrundy数を当てはめてみると、まず11は0である(このゲームの終わりなので)

そして、10は11に遷移できるので、grundy数は1

9は、10,11のどちらにも遷移できるので、0,1以外の最小の非負整数2をとる

8は、9,10に遷移するので、9,10のgrundy数2,1以外の最小の非負整数の0をとる

......とやっていくと、0~11とgrundy数の対応表は以下の通り

 

数字    0   1   2   3   4   5   6   7   8   9   10   11

grundy数  2   1   0   2   1   0   2   1   0   2    1     0

 

これを見ると、

grundy数が0でなければ、必ず一手でgrundy数を0にできる

grundy数が0であれば、遷移先のgrundy数は0ではない

となる

必勝法を考える上ではこの言い回しはやや分かり辛いが、結局のところ、先手が自分であるならば、もし自分のターンでgrundy数でを0にした状態で相手に回したら、次の相手のターンでは、相手はgrundy数を0でない状態にしてこちらに回すことになるので、こちらが最適な行動をとれば、相手に負け確定の9,10(9,10はgrundy数が2,1)を押し付けることができる、ということになる

先手は0から始めて2,5,8,11の順で必ず言うことができるので、こうすれば必ず勝つことができる

 

ニムもこれと本質的には同じで、最後grundy数(もしくはXOR)を0にした方が勝ちというゲームの場合、

初期条件grundy数(XOR)が0ではない→先手必勝

初期条件grundy数(XOR)0→後手必勝

という風になる

0にした方が負け、という場合は、先手と後手が逆転する

 

さて長々書いてきたわけだが、このゲームでも同じように考えるとどうやら山の石の数が(0,0),(0,1),(1,0),(1,1)がgrundy数=0の状況になるので、最終的にこの状況に自ら持っていくことができれば勝てるということになる つまり最初のgrundy数が0でなければ先手必勝、そうでなければ後手必勝

逆算して石を増やしていくと、grundy数==0となる場合は、二つの山の石の個数の差が1以下であるときになるそうです(これはちょっと実装できてないので確かめられない)

例えば石の数が(2,1)なら、初期条件のgrundy数は0で、Alice(先手)が山を(0,2)にしてBrown(後手)に回さなければならず(この時点でgrundy数!=0)、Brown(後手)は(1,0)でAliceに回すことになり、この時点でgrundy数は0になったことになり、初期条件のgrundy数==0→後手必勝が分かります

これはまたあとで実装力をつけたらやりたいと思います トホホ

2/27 AtCoder(小数点以下の四捨五入、直積(itertools.product())、いもす法(最大被覆区間))

こんにちは。競プロ合宿二日目のとーです。

 

AtCoder

解くに至った問題(8問)

ABC:001C/005C/006C/015C/017C/028C/030C/057B

 

ABC001C

四捨五入をうまく扱えますか?的な問題

まず、調べて普通に出てきた組み込み関数round()を使ったところうまく動いてくれない

python3のround()の特徴として、

①0.5は、丸めた結果が偶数になる方に動く

[例]

round(1.5)→2

round(2.5)→2

round(3.5)→4

round(4.5)→4

②0.05......と小数点以下第二位よりも小さいところをround()で丸めると、①に当てはまらない場合が出てくる

[例]

round(0.35,1)→0.4

round(0.45,1)→0.5

これは、少数を浮動小数点で正確に表せないために起こってしまう

 

さて、四捨五入の他のやり方として

f =(何らかの与えられた数)を小数点以下第二位で四捨五入したいときは

P = Decimal(str(f)).quantize(Decimal('0.1'), rounding=ROUND_HALF_UP)

とするといけなくもない(0.1の部分を0.01と返れば、第三位で四捨五入ができる)

ただ、これも扱いに注意が必要で、例えば

f = 32.65

という値は、上式に代入すれば、

P = 32.7

と返ってきて、一見よさそうだが、

P < 32.7

はTrueになる これはDecimalうんたらで表示桁を小数点以下下一桁にしてあるだけで、(見た眼的には四捨五入がうまく行ってるように見える)実のところ32.7より細かい情報は保持したままになっているからである

だから、本質的には32.65 < 32.7の比較を行っていることと同じになってしまう

これを解消する(つまり四捨五入した後のPを32.7として扱いたい)場合には、Pをfloatに変換すればよい そうすれば、Pが実は32.7より小さいんだという細かい情報が落ちて、P = 32.7として扱えるようになる

P = Decimal(str(f)).quantize(Decimal('0.1'), rounding=ROUND_HALF_UP)

P = float(P)

とすればよい

 

さて、長々書いてきたが、こういった微妙な扱いを避けるために、四捨五入を自分で定義してしまうのが一番手っ取り早い

fを「四捨五入したい数」、digitを「少数第何位で四捨五入したいか」として

f,digit = map(float,input().split())
def my_round(f, digit):
    p = 10**digit
    return (f*p*2+1)//2/p

とすれば、これでうまく四捨五入ができたことになる

[例] f, digitを入力として、

my_round(32.65,0) → 33.0

my_round(32.65,1) → 32.7      # これはmy_round(32.65) < 32.7でFalseを返す

my_round(32.655,2) → 32.66

 

 

ABC015C

L = [[1,2],[4,5]]

というリストがあり、

L[0],L[1]から要素を一つずつ選んでくる(直積を求める)ようなコードを書きたい

 

itertoolsのproductというのを使うとよいらしい

文頭にimport itertoolsとつけてから使う

 

import itertools

M = list(itertools.product(*L))    #*LはL内の要素を表し、こうすればLに内包されたリスト

                の数が増えても問題ない

 

とすれば、M = [[1,4],[1,5],[2,4],[2,5]]となる

atcoder.jp

 

 

ABC017C

長さがMの区間がある その中のいくつかの区間にポイントが与えられており、長さMの区間をすべて被覆しないように区間を一つずつ選んで(区間同士がかぶってもよい)、その合計ポイントを最大化する問題

いもす法を使う

実は以前に触れたことのある手法だったのだけれど、ここでも使えるのかと驚きました

 

具体例

温泉にi人の人が入りました p番目の人が入った時刻はH_j、温泉から上がった時刻はD_jです(時刻D_jまで温泉に入っていたことにする) この温泉には同時に最大何人入っていたでしょう?

例えば、H_j、D_jの入力が

1 4

2 6

3 5

4 7

4 6

みたいに与えられていたら、リストLを

L = [0,0,0,0,0,0,0,0]

と作っておく これは、L[t]が時刻t-1に何人が温泉に入っていたかを表すリストになる

ここでは、上から入力を受け取った順に、

L[H_j-1] += 1 (温泉に人が入った)

L[D_j] -= 1  (温泉から人が上がった)

とすると、

L = [1,1,1,2,-1,-1,-2,-1]

となる

これを前から順に足していったら、

L = [1,2,3,5,4,3,1,0]

となり、時刻4で最も多く人が温泉に入っていたと分かる、というような手法

(今上で挙げたコードでは最後が0になっている ここに注意する)

 

さて、上の例では「最も多く被覆している部分はどこですか」というのを求めるためにいもす法を用いたわけだが、今回は「被覆しないところを残しつつポイントを高くしたい」というのが目的である

結論から言うと、一か所でも被覆していないところがあればよいので、まず全部被覆したとして、合計ポイントを加算する そこからいもす法と同じやり方で、区間の各点におけるポイントを計算し、リストに保持しておく

このリストは、その点を被覆する区間が持つポイントを表している(このポイントは、複数の区間の合計ポイントであることもあり得る なぜなら各点を被覆する区間は単一とは限らないので)

そして、それが求め終わったら、リストをソートして、最も小さい(上の例で言えば、ソート後の一番最初に0がくるのはわかりきっているので、二番目に小さい)ものを選ぶ それが、除くべき最小のポイント区間であり、逆にここを除くことで、その点上の区間は被覆されていないようにすることができる

 

という流れで答えを求めることができる

 

atcoder.jp

 

 

解けなかった問題

ABC:029D

2/26 AtCoder(絶対値を外す際の全探索)

こんにちは。競プロ合宿1日目のとーです。

 

AtCoder

解くに至った問題(3問)

ARC:081D/100D

Tenka 1 Programmer Beginner Contest:C

 

ARC100D

x_i,y_i,z_iがN個の要素として与えられて、(x_iの絶対値のM個の合計)+(y_iの絶対値のM個の合計)+(z_iの絶対値のM個の合計)を最大化する問題

 

要素がプラス、マイナスどちらの形式でも与えられているのが厄介

 

純化して考えると、例えばx_i+y_i+z_iのM個の合計を最大化したいのであれば、これは簡単でx_i+y_i+z_iの合計をソートしてM個上から採用すればよい

 

これを応用して、結局絶対値を外す作業はプラスマイナスを逆にする作業なので、絶対値の合計として求めるならば、opt = ['+','-']として、例えば+x_i+y_i-z_iを値の大きい順にソートし、上からM個の合計をとれば、z_iが負の値ならばそれを正の形に変換したことになる(もちろんz_iが負になるとは限らないので、これが正だったら答えから外れる(マイナスすれば和が小さくなるので))。これを、optについて全探索(8通り)して、最も大きいものが答えとなる

 

解けなかった問題

ABC:087D/092C/097D

2/24 AtCoder(数列の部分和、ありうる和をDPで求める、数直線上を動き二か所を訪れる最短経路(番兵))

こんにちは。金恋GTが届いてウキウキのとーです。「とー」と申します。はじめましての人は初めまして。

 

sagaplanets.product.co.jp

 

AtCoder

解くに至った問題(9問)

ARC:042A/054B/074C

ABC:036C/037C/079C/118A,B,D

Typical DP Contest:A

 

ここ二週間でたまった直しを消化していく

 

ABC037C

tooh.hatenadiary.jp

数列の部分和の問題再び 以前の問題では「部分和が0になる区間の選び方はいくつあるか」という話で、第i項までの和をリストに保持しておいて一致する部分の二個の選び方を求めればよいというものだった

今回は、数列のK項の和をどんどんずらして足していくとなんぼになるんじゃいという問題

愚直に足すと、最悪O(N**2)かかって間に合わないが、今回も和をリストに保持しておくやり方が使える

第i項までのリストはO(N)で計算でき、それから区間Kの和は、和のリストLの要素から、L[i+K]-L[i]とO(1)で計算される これなら間に合う

数列の和の問題は総和のリストを作ることを頭に入れておく

 

Typical DP Contest A

与えられた数字を足し合わせて作ることができる数字は何通りありますかという問題

制約は「与えられる数字は100以下、100個以下」

これをDPを使って解くときは、

①0が10001個あるリストdpを作る

②dp[0] = 1にする(1は「その数字が作れる」ことを意味する)

③与えられた数字kに対し、dp[10000]から下っていくように見ていき、dp[i] == 1であればdp[i+k] = 1(つまり和が作れる)とする

(ここでdp[0]からではなくdp[10000]から見ていくことに注意。もし下から見ていくと、例えばdp[0] == 1だからdp[k] = 1となり、今度はdp[k] = 1だからdp[2*k] = 1となり......と実際には作れない数字も作れることになってしまう)

④リストdpには作れる数字の所のみ1が立っているので、総和を計算すれば作れる数字の個数が分かる

という流れ

 

ABC118D

数直線上にある寺と神社を、スタート地点からどちらも一件だけ訪れるのに必要な最短経路を求める問題

コンテスト中にACできなかったのは悔しい 二分探索や、神社→寺or寺→神社で場合分けすることを思いついたものの根本的なところが間違えていた模様

まず第一の失敗として、二分探索で自分の居場所を特定する際に、普通に与えられた入力だと、例えば自分が一番西にある神社より西にいる場合、訪れる神社が一つしか候補にあがらず、場合分けが非常にめんどくさくなるというものだった 

これを回避するために、両サイドに一番西/東にある座標よりとてつもなく大きいものを座標として仕込んでおく方法がある これを番兵(ばんぺい)という

こうすれば、どこに始点があっても自分の両側に神社/寺が存在し、余計な場合分けを考えなくてすむようになる

さらに第二の失敗として、最短経路の出し方を、「まず自分がいるところから最も近い神社に向かう→そこから最も近い寺に向かう、この距離を求める、これを神社と寺を入れ替えてもう一度やる」というものだったが、これには普通に例外があり(サンプルケースでは通ってしまう)

例えば

 

寺 x  寺  神社 寺

0 1    3     5        6

 

というケースを考えたとき、(xは初期位置、下の数字は座標)

上の考え方では

寺(0)→神社(5)と神社(5)→寺(6)という経路が採用され、経路の長さ自体は各々6,6である

しかし、右に進めば寺(3)→神社(5)となり、経路の長さは4でより短く、これは上のやり方からは漏れてしまうような場合である

 

結局、一意に最小を決定するのは難しいので、自分より右、もしくは左にある最も近い神社と寺(どちらも2つある)を見て、

「神社と寺、どちらを先に訪れるか」*2

「神社と寺、自分より右にあるものを訪れるのか、それとも左を訪れるのか」*2*2

の計8通りをすべて試し、その中で最短のものを答えとして採用すればよいということになる

 

自分でもとめたやり方は案外間違っていることが多いので、複数候補がある(どれが答えになるか決めがたい)ときは、一回候補を全部挙げてみて、その中で比較するというやり方をちゃんと使えるようになりたい

 

2/23 AtCoder

AtCoder

解くに至った問題(5問)

ABC:004C/019C/023B/024B

AGC:004A

 

 

解けなかった問題

ABC:009D/021C/034C

 

明日ABC生えてるので復習します 

ABC034Cは逆元という考え方を使うらしい 10**9+7は素数なので、A/Bをこれで割るときはBを10**9+5乗をしてから10**9+7で割り、それとAをかけたものをまた10**9+7で割ればよいとのこと  なんだこれは

2/22 ウニ(Sqlupp)/AtCoder(ナップサック問題、ゲームの必勝法(ニムなど、DFS、ビット演算子、XOR))

ウニ

 f:id:saguh:20190222165744p:plain

Sqluppを解禁  鳥取りました  わいわい

楽しいしガンガン腕を動かせてうおお俺今チュナイズムやってるな~~~~~~空間を切り裂いている新音ゲーやな~~~~~~~~となれる曲ですがなかなか難しいです

鳥取るのに15回くらいかかりました  以下攻略というと仰々しいですが、自分が気をつけたところを書いていきます

画像は全てCHUNITHM譜面保管所様よりお借りしました ありがとうございましたhttp://www.sdvx.in/chunithm/04/04069mst.htm

 

f:id:saguh:20190222170444p:plain

アヘ顔おじさん

 

 

 

f:id:saguh:20190222170520p:plain

7,8小節目、難所とも言い難い所なんですけど1/5くらいの確率で失敗しました  

画像より前の部分のホールド+タップを人差し指薬指で処理するイメージを持って、そのままの流れで拘束スライドを薬指、タップを人差し指で処理すると安定します

 

 

f:id:saguh:20190222170758p:plain

37~43小節目  ホールド+タップ+エアーの認識難地帯です  ここは気合いで行くしかありません  意識したことは両端のホールドとタップは薬指、真ん中のホールドとタップは人差し指で押さえました  これは人差し指じゃなくて親指でやった方が良い人もいると思うので個人差かも

 

 

f:id:saguh:20190222171106p:plain

45~56小節目  楽しいところ  ただ巻き込みアタックが出やすいです  叩くところをちゃんと意識するのと、気持ち指を浮かせると巻き込みにくくなります

 

 

f:id:saguh:20190222171236p:plain

48,49小節目のここ、終点の判定が厳しいです  特に最後はフリックを早めに擦り始めると普通にAttackが出るので要注意

 

 

f:id:saguh:20190222171536p:plain

61,62小節目  何故かアタが出やすいです  最後は右利きの人も左手から入った方が良さそう

 

 

ここまでは前半戦です  鳥狙う時はここまででアタ換算2~3くらいに抑えとくと後が楽

後半は大きな難所が二ヶ所来ます

 

 

f:id:saguh:20190222171746p:plain

67~69小節目  後半の難所の一つ目  トランペットの音に合わせて叩くのですがここめちゃムズいね  

構造は見ればわかる通り全部右始動の階段+トリルです

僕は餡蜜が全くできないので素直に目押しするしかなかったのですが、どうしようもないのでとりあえず右手の方をガン見してました  これだけでもアタが2,3くらいに抑えられます

 

 

f:id:saguh:20190222172353p:plain

77~79小節目  難所その二です 

まずは親の顔より見たWarcry地帯  ここは3鍵が4つ、4鍵1つ、3鍵が3つになっててパワーアップしてます  やめろ

ここも餡蜜できればいいんですけど、しょうがないから前半4つは目押し、後半4つにかけて餡蜜っぽく交互押しで無理やり通しました

 

そのあと78小節目からは交互なんですが、だんだん右にずれていくので巻き込みアタックが非常に出やすいです  注意したいところ

 

ここまでアタ9で来れたら後をAJ通過すれば勝ちです

 

 

......と言いたいところですが最後にラッシュがやってきます

 

 

f:id:saguh:20190222173153p:plain

この部分、単純な交互トリルですが切れ目(緑のところ)がいやらしいところについていて抜けが出やすいです  ここをちゃんと意識してシバくようにしましょう

 

あとは回数を積めばなんとかなります  金が全てを解決する

 

 

AtCoder

解くに至った問題(6問)

ABC:035B/036B/037B/038B/039B

AGC:015B

 

解けなかった問題

ABC:067D

DPまとめコン:D

 

ABC067D

 

木(どのマスも経路を介せば辿っていけるグラフ)をぬりぬりしていく問題

スタートのマスは決まっていて、二人が交互に色塗りしていく

相手が塗れなくなったらこっちの勝ちという趣旨のゲーム

 

最適な戦略は、相手をなるべく塗らせないようにすればよいのだから、相手のスタートのマスに向けてできるだけ遠くまで塗った方がよい(遠くまで塗っておけば、それより近くのマスは塗ることができるので)ということ これでどっちが勝つのかを判定する

 

先手はマス1から始め、隣り合うマスを黒く塗り、後手はマスNから始め、隣り合うマスを白く塗っていく

 

ゲーム自体は、「最適戦略をとったときに、お互いが塗ることが可能なマス目の数を比較して、多い方が勝ち」、「マス目の数が同数になる場合は、後手が塗って同数になり、マス目をすべて塗り終えたことになるので、先手が塗れない→後手の勝ち」とルールを変えても全く同じである

 

ここまでわかれば、あとはマス1とマスNからすべてのマス目の距離を比較して、近い方の色にぬればよいことが分かる

 

具体的には、マスiとマスjの距離をd(i,j)としたとき、マスiを黒に塗るときは

d(1,i) <= d(N,i) (1からの距離の方が近い、同じなら先手が塗る)

となる場合である

 

このようなマス目を計算するのは深さ優先探索を用いることでできる

 

以前書いたDFSの記事では「経路が一筆書きできるか(木かどうか)」を判定するものだったが、今回もそれに似たものが使えて、距離のデータを保持しておく必要はない

 

さて、DFSでは最初に、隣接する島のデータを格納するリストを作る必要がある 

以下にコードを書いていく(0インデックスで数字が一つ前にずれていることに注意)

 

G = [ for _ in range(N)]  #i番目の空のリストには島iに隣接する島の番号が入る

for i in range(N-1):

    a,b = map(int,input().split())

    G[a-1].append(b-1)

    G[b-1].append(a-1)         #隣接する島のデータを入れる

 

これで島の隣接についてはよい あとは、

visited = [0]*N

visited[0] = 1

visited[N-1] = 1

visited_all = [1]*N

として、「i番目の島が訪れられたかどうか」を格納するリストを作っておく(島0と島N-1はスタート地点なので、すでに訪れられたものとした)

 

そして、

nextA = G[0]

nextB = G[N-1]

とする これはA(先手)が次に訪れるのはG[0]、つまり0番目の島に隣接した島である、というような考え方で理解できる Bについても同様

 

さらに、

black = 1

white = 1

とし、ここで黒、白で塗れるマス目の数を保持しておく

 

ここから塗る作業に入っていく

while visited != visited_all:

    l = [ ]

    for a in nextA:                 #先手が次に塗る島を考える

     if visited[a] == 0:

            visited[a] = 1

            black += 1

            for x in G[a]:                #塗り終えた島について、その島と隣り合う島を見る

                l.append(G[a])        #lに今黒く塗った島と隣り合う島のデータを入れ、

            nextA = l                      #nextAの情報を更新  ここから下は後手の話になる

    l = [ ]

    for b in nextB:

        if visited[b] == 0:

            visited[b] = 1

            white += 1

            for y in G[b]:

                  l.append(G[b])

            nextB = l

 

これをvisited == visited_allとなるまで繰り返す

ここまでくれば、あとはblackとwhiteを比較したうえで、black > whiteならば先手の勝ち、それ以外ならば後手の勝ちとすればよい

 

 

ゲームの必勝法問題はほかにもいっぱいある(はず)

石取りゲーム(ニム)を例にとって挙げてみる(蟻本のp.277に載ってる)

 

[問題]

石の山がn個あり、i番目の山にはa_i個の石がある。AとBは交互に空でない山を一つ選んで、そこから一つ以上の石をとる。最後の石を取った方を勝ちとする。Aから始めた場合、両者が最善を尽くせばどちらが勝つか。

 

石の山全部の石の個数のXOR(二進法の各桁を比較して異なっていれば1、同じなら0)を考える XOR = 00....0も0と扱う

XORを計算する際、計算して各桁を1,0のみにするのは面倒なので、二進法で表したときにそれをそのまま累積していって、各桁の偶奇を見る方法を利用する

(例)

2 XOR 5 XOR 7

は2,5,7を二進法に直すと10,101,111でそのまま足すと222になり、これは各桁が偶数なのでXORを二進数で直した結果は0が並ぶ(000)

 

2 XOR 3 XOR 3

は2,3を二進法に直すと10,11でそのまま足すと32になり、XORは10

 

最終的に自分が石をとって終わりにしたいので、自分が勝つときの最後の局面でのXORは0(0+0+......+0 = 0なのでそれはそう)

ということは、ここから逆算して、自分の手番が終了したときに常にXORを0にできれば必ず勝てる(逆に、相手の手番が終了したときXOR != 0であればよい)

 

さて、これが通用する理由は二つあり、

①XOR != 0で回ってきたらXOR == 0にして返せる

②XOR == 0で回ってきたらどうやってもXOR != 0にしかできない

からである

 

②は、0が並んだ状態からはどこをとってもXORが0でなくなってしまう(0を0のままで返せるのは0の足し算のみ)のでわかる

 

①は、例えば2,4,5を考えたとき、2,4,5は二進法に直すと10,100,101でXORは011、これを000にする(XORを0にする)ためには、011のうち下二つの1を0にする(反転させる)必要がある

やり方は、

ア、XOR結果を二進数で見たときの一番上の1のビットが経っている山を選ぶ。つまり、今回の場合は011で、1のビットが経っているのは上から二番目。2,4,5(010,100,101)の中で上から二番目の1のビットが立っている山は2(010)なのでこれを選ぶ。

イ、選んだあと、011の下二つを反転させたいので、2(010)の下二つを反転させるように石を除く(つまり、1個除いて山を1(001)にする)。これでXORを0にして返せる

これにより、XOR != 0の状態からXOR == 0にして返せることが分かる

    

これによって、開始時のXOR結果が0であれば、Aは必ずXORを0でない形で返さねばならず、Bが最善策をとれば必ずXORを0にして返すので、Bが最後にXORを0にして勝ち。一方で、開始時のXOR結果が0でなければAの勝ちとなる

 

つまり石を動かしてアレコレやる必要もなく、最初の石の山の状態でどちらが勝つか決まっている これを考えてプログラムを書いてやればよいことになる

 

 python3で数字を二進法表記した結果を用いて演算してくれる演算子(ビット演算子)は、&、|、^がある

 

a&bはa,bを二進数に直してどちらも1なら1,異なっていれば0を返す

例えば11&14 == 10である

11,14は二進法に直すと1011,1110なので、両方1を持つ桁を1、そうでない桁は0とすると、1010となり、これを十進法に直せば10だからである

 

a|bは、二進法に直したとき少なくとも1つが1なら1を返してくれる

11|14 == 15 (1011と1110なので1110と返ってくる)

 

a^bが今回使うXORで、異なっていれば1、同じなら0を返す

11^14 == 5 (1011と1110なので0101が返ってくる)

 

紹介が終わったところで答えのコードを書く

 

n = int(input())
L = list(map(int,input().split()))
S = 0
for i in range(n):
    S ^= L[i]
if S != 0:
    print('A')
else:
    print('B')

 

これだけ 単純だね かんたん わいわい

 

 

DPまとめコンD

たぶん一番基本的な問題 いろいろ言う前にもう答えを覚えちゃった方がよさそう

重さについてのDPを使って解くのが素直

与えられた品物の個数をN、重さの制限をWとする (N+1)*(W+1)のリストdpを作ってあげる(品物の番号は0インデックスなので0~N-1)

dp =
for i in range(N+1):
    dp.append([0 for _ in range(W+1)])

縦の番号には0~Nが入り、横には0~Wが入る

それから、i番目以降の品物から重さの総和がj以下になるように選んだ時の価値の最大値をリストの中に順次入れていく

(重さと価値のデータはwvというリストに入れることにする)

リストの下の方から埋めていく

まず、リストの一番下は、N番目以降の品物を選ぶことになるので、品物がN-1番目までしかない以上は選べないので0で埋めていくしかない(すでに0で埋まっているので放置)

それから、(N-1)~0番目について、i番目以降の品物を選ぶ際のアルゴリズム

for i in range(N-1,-1,-1):         #N-1から0まで逆順に見ていく

    for j in range(0,W+1):

if j < wv[i][0]:                           #wv[i][0]が制限重量をオーバーしていたら

    dp[i][j] = dp[i+1][j]          #その品物は入らないのでdp[i+1][j]がそのまま入る

else:

    dp[i][j] = max(dp[i+1][j],dp[i+1][j-wv[i][0]]+wv[i][1])      #w[i]を入れるかどうかを判定する

                                                                                         ために、それを入れた場合と入

                                                                                         れなかった場合とを比較する

print(dp[0][W])

 

これでいける 

いけませんでした 言語的な問題がありそう 計算量が10**7で、ここまでくるとpython3は厳しいのかもしれない numpyをお勉強する必要があるかもしれない 

 

2/21 AtCoder(アルファベットのリスト、二進法表記、深さ優先探索)/ウニ

 AtCoder

解くに至った問題(4問)

ABC:014B/058C/064C/070C

 

 

ABC014B

与えられた数Xの二進法表記を求めるプログラム

X = int(input())

bi = ''

p = X
for i in range(1,n+1):                 #2**n > Xとなるようにnを選ぶ
    if 2**(i-1) <= X < 2**i:
        k = i
        break
for j in range(k):
    if p >= 2**(k-j-1):
        p -= 2**(k-j-1)
        bi += '1'
    else:
    bi += '0'

 

ABC058C

aからzまでの26文字のアルファベットが入ったリストが欲しいなら、

alp = [chr(ord('a') + i) for i in range(26)]

とすればよい

 

 解けなかった問題

ABC054C

昨日のUF木を使って解く問題かな~とか安直に考えてたら、今回は除く辺のパターンが2**(N**2)くらいあるのでループで書くのがめちゃんこ難しい

ここでは深さ優先探索(DFSというらしい)を利用して解くのがよいとのこと

島N個、島と島を結ぶ橋をM本とする

(問題では島は1,2,3......と番号が振られていたが、ここでは0,1,2......と扱う)

 

N, M = map(int, input().split())
G = [[] for i in range(N)]    #G[i]は、i番目の島と結ばれている島の番号を格納する
for i in range(M):
    a, b = map(int, input().split())  #↑のやり方で、橋で結ばれた島をリストに入れていく
    G[a - 1].append(b - 1)
    G[b - 1].append(a - 1)
cnt = [0]            #cntは、一筆書きできる場合の数を表す
def dfs(V, s):                              #リストVに着目、sは一筆書きをスタートする島である
    V[s] = 1                                  #sの島を訪れたことを示すため、まずV[s]を1にする
    if sum(V) == N:       #全ての島を訪れ終わると、リストVの要素は1で尽くさ          

                れているハズ
        cnt[0] += 1                         #そのような場合は今回の題意に適するので、cntを+1
    else:            #リストVが1で尽くされていない場合は、
        for adj in G[s]:      #今はs番目の島を見ているため、s番目の島と結ばれた島

                  を次に考える(訪れる)
            if V[adj] == 0:                #s番目の島と結ばれた島がVのリストで0になっていたら
                dfs(V[:adj] + [1] + V[adj + 1:], adj) #その島を1(訪れたことを表す)にする

                   さらにsをadj(次に訪れた島の番号)に変更
dfs([0] * N, 0)          #今回は、これを0番目の島からスタートさせる 
print(cnt[0])

 

例えば

3 3

1 2

1 3

2 3

という入力であれば、島1,2,3が三角形のようにつながっていることになるが、

これを上のプログラムに当てはめると、

 

島0(1)から一筆書きをスタート→

V = [1,0,0]に→隣接している島は島1(2)と島2(3)なので、こいつらを次に見て、V = [1,1,0] とV = [1,0,1]と新たに二つのリストを作る→

それをもとに、V = [1,1,0]についてdfs(V,1)とV = [1,0,1]についてdfs(V,2)を行う→

dfs(V,1)については、島1(2)と隣接する島が島0(1)と島2(3)であるが、V[0] == 1だからこれはもう見ないで、V[2] == 0についてのみ考える→

結局新たにV = [1,1,1]というリストができる これはsum(V) == 3を満たしているため、島0からスタートしてすべての島を訪れたことになっている→

これは題意に適するため、カウンターを1増やす→

dfs(V,2)についても同様の事から、カウンターの値が1増える 最終的に2パターンの経路で一筆書きができることがわかる

 

また一人で作りなおしてみます

atcoder.jp

今回もまたじゅっぴーさんにお世話になりました ありがとうございました

 

 

 

 ウニ

f:id:saguh:20190222025554j:plainf:id:saguh:20190222025609j:plain

昨日二週間ぶりくらいにやって今日アプデがあったのでやってきました

マップの新曲は二つしか解禁できてないですがどっちも曲も良くて楽しかったしキャラも可愛いしで文句ありません  Techno Kitchen脳トレやめろ

 

CITRUS MONSTERが問題で、多分10回くらいやったんですけど6954で止まってしまい鳥は乗りませんでした  早いトリルとかどうやって力入れて叩けばいいっちゅうねん  気合いで乗り切るしか無いですね

 

2/20 AtCoder(Union Find)

AtCoder

解くに至った問題(2問)

ABC:076C

AtCoder Typical Contest001:B

 

AtCoder Typical Contest001:B

Union Find木についての問題 これを解く前にABC075Cの連結判定の問題をにらみつけて時間を溶かしていた そういうのの解法はいろいろあるけど、辺の連結を判定するにはUF木がよいとのこと(チーター本のp.370くらいに載ってた これマジでC問題か?)

 

UF木のUの字も聞いたことのないプログラミングド素人が適当にかみ砕きながらあくまで自分のためだけに説明する

 

UFとは社会である 子会社のもとをたどれば親会社に、そして部下の上には必ず上司がいるように.......

 

簡単に言えばどの要素とどの要素が連結しているか(この連結は、何か他の要素を介して辿れるものもふくめて)を便利に扱えるもの 

予めゴールを決めておいて、同じ集合の要素であれば、どの要素からも遡っていった先がそのゴールにたどり着くような設計になっている このゴールの事を根(多分「ね」って読むんでしょう)という これが集合の要素を代表している値である

 

要素がn個あり、m本の辺で連結されているとしよう

まず、par(多分parentの略)とrankという空のリストを用意しておく

それから、

par = [0,1,2,......,n-1]  #n個の要素

rank = [0,0,0,......,0] #n個の要素

とする

parは、par[i]を上にたどっていった先の「根」が何かを表すものである

rankは、その「根」である要素jに対し、rank[j]が根の深さを表す。木のように要素が辺で連なっていると考えたとき、末端の要素から根がめっちゃ離れていればrank[j]は大きくなる

UFを扱うための操作として、findとuniteとsameという三つがある

 

find(x,par)

これは、要素xがparの中で、どれを根としているのかを表している したがって

def find(x,par):

  if x == par[x]:        #これはx自身が根であることを表している

    return x

  else:

    return find(par[x],par)       #par[x]はxの上にあるものを表す par[x]を辿ることで、最  

                                               終的にxの根が見つかる

 

unite(x,y,par,rank) 

これは、要素xと要素yが属する集合をくっつける操作である

def unite(x,y,par,rank):

  x = find(x,par)

  y = find(y,par)           #x,yの根を見つける

  if x != y:       #x,yの根が違えば、

    if rank[x] < rank[y]:   #根っこの深さを比較し、根っこが深い方に浅い方をくっつける

       par[x] = y     (この場合、xの根をyに変更する)

   else:

       par[y] = x

       if rank[x] == rank[y]:  #根っこの深さが同じ場合は片側のランクを+1する

          rank[x] += 1

 

same(x,y,par)

これは、x,yの根を比較するための操作である

def same(x,y,par):

  return find(x,par) == find(y,par)     #Trueなら1、Falseなら0が返ってくる

 

これらを組み合わせれば、晴れてUF木の扱いができる

例えば、AtCoder Typical ContestのB問題のように一つずつクエリが与えられた際には、①要素と要素をつなげる場合にはuniteを使えばよいし、②要素と要素が連結しているかどうかを判定する場合にはsameを使って判定すればよい

(今回の実装は0インデックスなので、例えば島の番号が1から始まっていたりしたら、入力を-1するといったことに注意する)

 

だから、ABC075Cの問題は、縦に与えられた入力を「要素を辺でつなげる操作」と解釈し、例えば1番目の辺を除いたすべてをUF木としたとき、連結していれば(つまり、取り除いた1番目の辺が橋でないならば)すべての要素の根が一致するはずであると考えることで、橋の本数は、それをM番目の辺までやったときに、連結していない(根がすべての要素で一致しなかった)UF木の数を数えてやれば求まると分かる また今度やってみます

 

今回もじゅっぴーダイアリーにお世話になりました ありがとうございました

juppy.hatenablog.com

2/19 AtCoder(二分探索(bisect)、変数を文字で指定した後計算するeval、多重ループを抜けるexit())

AtCoder

解くに至った問題(7問)

ABC:009B/010B/057C/061C/096D

ARC:059D/084C

 

ARC084C

3つのリストA,B,Cを所持し、1<=i<=Nに対し、Aの中からBのi番目の要素より小さいもの、Cの中からBのi番目の要素より大きいものの個数をそれぞれ求め、掛け合わせた後でその合計を求めていくという方針

問題を眺めれば方針は立つものの、実装するときに1つのBの要素に対しA,Cの要素N個を見ていたらN^2の計算量になりTLE そこで、今回は二分探索を使う

Python3では二分探索のライブラリが存在する

import bisectを一行目に書いてから使う

bisectには、bisect_leftとbisect_rightが存在する。どちらも、検索したい数字がソート済み(昇順)リストの中になければ、リストの中にその数字を入れると何番目に入るかを教えてくれる(インデックスを返す)が、1つ以上存在したときにその扱いが異なってくる

①bisect_left 

これは「ある数字未満の数字がリストの中にいくつあるか」という認識でよい 数字が既存の値の左側に入る

 

A = [1,2,4,4,5,6]に対し、

k = bisect.bisect_left(A,3)とすれば、

k = 2 と返ってきます   (A = [1,2,3,4,4,5,6]で、3が入るとしたらリストの2番目)

また、

k = bisect.bisect_left(A,4)とすれば、

k = 2 と返ってきます   (A = [1,2,4,4,4,5,6]で、既存の4の左側に入る)

 

②bisect_right

これは「ある数字以下の数字がリストの中にいくつあるか」という認識でよい 数字が既存の値の右側に入る

 

A = [1,2,4,4,5,6]に対し、

k = bisect.bisect_right(A,3)とすれば、

k = 2 と返ってきます   (A = [1,2,3,4,4,5,6]で、3が入るとしたらリストの2番目)

また、

k = bisect.bisect_right(A,4)とすれば、

k = 4 と返ってきます   (A = [1,2,4,4,4,5,6]で、既存の4の左側に入る)

 

ちなみに、bisect.insort_left(A,n)とすれば、leftで行ったのと同じ手順で、nをリストAの中に入れてくれる もちろんbisect.insort_right(A,n)もある

 

ARC059D

アンバランスな文字列

文字列が与えられて、その部分列の中に同じアルファベットが部分列の長さの過半数を占めている部分を抜き出せという問題

ろくに考えもせず解答を見たところ、XXもしくはXYXという部分列が存在しなければアンバランスな文字列は存在しないとのこと 

それはそう 

過半数を占めればよいので長さ2のうちの2もしくは3のうちの2を同じ文字が占めているところがあればそこを抜けばよいし、なければ無いと返せばよい

別にめちゃんこ長い部分を抜き出す必要はないのでこの程度で十分 もっと頭を使おうね

 

解けなかった問題

Tenka 1 Programmer Beginner Contest2018:C

ABC:079C

 

天下一C

数列で、隣り合う数の絶対値の合計を最大化する問題 リストで保持した値をソートし、例えば要素が5個であれば

p1 <= p2 >= p3 <= p4 >= p5

とするのか

p1 >= p2 <= p3 >= p4 <= p5

とするのかを二通り確かめる

さらに、プラスは下の場合、p1+2*p3+p5となるので、p3にはリストで最もでかいのを持ってくるべき

などの工夫を使って解く問題 こんがらがって一生できなかったので次の機会に

 

ABC079C

与えられた文字列の千の位、百の位、十の位、一の位の数字に対し、足し算もしくは引き算をした後で、答えが7になるような数式を出力しろという問題

cal = N[0]+op1+N[1]+op2+N[2]+op3+N[3]

と置くと(opnは演算子)これはあくまで文字列である

ここで、eval()を使うと、eval()は文字列を計算してくれる つまり

return(eval('1'))

は数字の1が返ってくるし、

return(eval('1+2'))

は数字の3が返ってくる

ここでは、 
if eval(cal) == 7:

  print(cal+'=7')

とすることで、calという文字列を計算してくれる

(cal+'=7'とすることに注意 calと=7は文字列なので接続する必要がある)

printではN[0]などに値が入る

 

また、

for i in range(N):

  for j in range(N):

みたいな、for文が連なっている時には、breakで抜けようとすると、for文の数だけbreakを書かないといけなくなる

exit()を使うことで、この手間を省き、一番内側のループをexit()で抜ければすべてのfor文のループを抜けることができる

note.nkmk.me

 

 

2/18 AtCoder(boolean配列、リストのn番目でソート)

AtCoder

解くに至った問題(8問)

ABC:066C/068C/082C/085D/088C/108C/109C/113C

 

ABC068C 

二つの島を行き来する定期便があり、往復する島がリストで与えられるので、一回の乗り換えで、島1→島i→島Nと行くことができるかを判定する問題

一個一個のリストを調べて[1,i]と[i,N]の存在を調べるのは非効率なので、ここではTrue、Falseを持ったリストを二つ作り、照らし合わせることを考える

具体的には、FalseをN+1個持ったリストJ,Kを作る。これを使って島1と島i、島iと島Nが結ばれているかどうかを判定する

島1と島iが結ばれていれば、リストJのi番目の要素をTrueに変更。島iと島Nが結ばれていれば、リストKのi番目の要素をTrueに変更。こうしてできた二つのリストを最初から見ていき、同じi番目がTrueを持っていれば、島iを経由して島1から島Nに行けると判断できる

 

ABC088C

実質連立方程式 変数の自由度が高ければ、1つ(ここではa_0)の値を0と仮定することで確かめることができたりする

 

ABC113C

11行目のところ

for k in range(M):
  if l == L[k][0]:
    cnt += 1
    L[k].append(cnt)

のcntとappendの位置が逆になっていたことが原因で昨日バグらせていた taiyoslime感謝します

d.hatena.ne.jp

リストLに対して、

L.sort()とすればリストの最初の要素でソートしてくれるけど、2番目の要素とかでソートしたいなーってなったら、

L = sorted(L, key = lambda x:x[1])

とすればよい 

 

 

2/17 AtCoder(エラトステネスの篩)

AtCoder

解くに至ったもの(4問)

ABC:117C/116C

みんプロ2019:D

全国統一プログラミング王決定戦:D

 

ABC117C

全く思いつかなかった

M個のチェックポイントにN個の駒を置いて、それらがチェックポイントをすべて移動しきるのにかかる経路の合計を最小にする問題

結局、N個の駒は移動することによりN個の区間を被覆することになる

実例を考えてみれば

1 2 3 4 5 6 7 8 9

を3つの駒で移動させることを考えると、例えば1,3,6に駒を置けば

[1,2],[3,4,5],[6,7,8,9]

と移動させることになり、区間は3つ

そうすると、考えるべきなのは、その区間にできるN-1個の境目の距離になる

例で言うと、[2,3]と[5,6]のような感じ

ここは、与えられたチェックポイントのうち、隣り合う2個のチェックポイントの距離にあたる(隣り合っているのは区間で被覆されているため)

そうすると、答えを出すうえで目指すべきなのは、全部は被覆するという前提を崩さず、いかにN-1個の境目の距離を大きくするかということになる

つまり、隣り合うチェックポイントの距離(N-1個)をソートして、上からN-1個取ってくる それを合計し、M個のチェックポイントのうち最も遠く離れた端と端の距離から引いたものが、求める駒の移動距離の最小値になる

 

解けなかったもの

ABC:084D/112C/113C

ABC084D

algoful.com

これを参考にして書いたコードがこちら

import math
L =
prime =

for i in range(2,100000):
  L.append(i)
M =
m = 0
while m+1 < math.sqrt(100000):
  m = L[0]
  prime.append(m)
  for i in L:
    if i%m == 0 :
      L.remove(i)

prime = prime+L
print(prime)

これだとTLEしてしまう 原因はremoveで、これは前から見ていって初めてその数字と同じになるやつを除くので、逐一検索していかないといけないため時間がかかる

(上のコードはせいぜい10^4までしか有用でない)

これを高速化する 参考にさせていただいたのはこちらのページ

juppy.hatenablog.com

#n以下の素数列挙(O(n log(n))
def primes(n):
  ass =
  is_prime = [True] * (n + 1)
  is_prime[0] = False
  is_prime[1] = False             ←ここまでで、素数判定のリストis_primeの要素の0,1番を         

             Falseにしたのと、ass(尻ではない)という、後に素数を

             入れる空のリストを作成した
  for i in range(2, int(n**0.5) + 1):
    if not is_prime[i]:              
      continue       ←ここでis_prime[i]がFalseならこれより下の文には進まな 

               い
    for j in range(i * 2, n + 1, i):  
       is_prime[j] = False  ←ここで、iの倍数をi*2からiの倍数ずつ見て、Falseにしてい 

             く
  for i in range(len(is_prime)):
    if is_prime[i]: 
      ass.append(i)                ←is_prime[i] == Trueであるiだけをリストassに追加していく
return ass

これで10^5でも十分速くなった ちなみにjuppyさんによるとdel,remove,insertは300点問題からだと実用性に欠けるとのこと 肝に銘じておきます

2/16 AtCoder(入力受け取りまとめ、数列の部分和、ユークリッドの互除法)/ss

AtCoder

解くに至った問題(15問)

キーエンスプログラミングコンテスト2019:A,B

エイジングプログラミングコンテスト2019:A,B

CADDi 2018 for Beginners:A

Tenka 1 Programmer Beginner Contest:2018A,B/2017B

CODE FESTIVAL 2016 Final:A

第5回 ドワンゴからの挑戦状 予選:A

AGC:023A/030A

ABC:058B/118A,B

 

listの中から未ACの100点問題が消えました わいわい 

 

CODE FES A

また入力で詰まった 一回入力を受け取る方法を整理しておいた方がよさそう

 思いつく限りでは、競プロC問題くらいまでで登場する入力のタイプは

① 'A'や'BC'といった文字列(一行、一列)

②1,23,456といった数字(一行、一列)

③'A BC'や'1 23'といったスペース区切りの文字列(一行、複数列)

④'A BC DEF......' や '1 2 3 4 5 .......'といったリストで受け取った方がいいもの(一行、複数列)

⑤1

 2

 3......という縦に並んだ入力(複数行、一列)

⑥1 2 3

 4 5 6

 7 8 9 といったような数字の行列(複数行、複数列)

⑦AB CD EF

 GH IJ KL

 MN OP QR といったような文字列の行列(複数行、複数列)

 

くらい(ほかにもありそうだけど) pythonでの受け取り方を整理

[3/1 ⑧模様の受け取りを追加しました]

 

 

 

① 'A'や'BC'といった文字列(一行、一列)

S = input()

 


②1,23,456といった数字(一行、一列)

N = int(input())

 


③'A BC'や'1 23'といったスペース区切りの文字列(一行、複数列)

文字列であれば、

S,T = input().split()

 

数字であれば、

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

とする

 


④'A BC DEF......' や '1 2 3 4 5 .......'といった、スペース区切りで要素数が多いため、リストで受け取った方がいいもの(一行、複数列)

文字列であれば、

L = input().split() か L = list(input().split())         (L = [input().split()]でもよい)

が使える 出力はどちらも同じなので前者を推します

 

数字の列であれば

L = list(map(int,input().split()))

でよい

 


⑤1
 2
 3......という縦に並んだ入力(複数行、一列)

文字列であれば、数がすくなければ、 

S = input()

T = input()

......

と個々に受け取ることができる

数が大きくなった時は、リストに入れることを考えて、

L =
for i in range(行の数):
  L.append(input())

とするのがよい

数字はinput() を int(input())に変えればよい

 


⑥1 2 3
 4 5 6
 7 8 9 といったような数字の行列(複数行、複数列)

L =

for i in range(行数):

  L.append(list(map(int,input().split())))

とすれば、

L = [[1,2,3],[4,5,6],[7,8,9]]

となる

リストの要素のは、

1は L[0][0]、6はL[1][2]などとあらわされる

L.sort()とするとLの一番目の要素(L[j][0])で小さい順にソートされる


⑦AB CD EF
 GH IJ KL
 MN OP QR といったような文字列の行列(複数行、複数列)

L =

for i in range(行数):

  L.append(input().split())

 

これで、L = [['AB','CD',EF'],['GH','IJ','KL'],['MN','OP','QR']]

となる

 

#.#

.#.

#.# みたいに#と.でマスが塗ってあるみたいなのを要素ごとで受け取りたい

L =

for i in range(N):               #Nは行数

  L.append(list(input()))

とすれば、

L = [['#','.','#'],['.','#','.'],['#','.','#']]

と受け取ってくれる 

 

[注意]input().split()ってやると[[#.#],[.#.],[#.#]]と入ってしまう

 

 

 

AGC023A

数列の部分和で0になる箇所がいくつあるかを数える問題 わからなかったんだけど解説見て賢すぎてハゲ散らかした

 

第0項から各項までの和をリストに加えていく この中で等しいものを二つ選ぶと、0~i項と0~j項の差は0→i~j項という部分列の和は0になる 結局そこまでの和が同じものの中から二つ選んでくる場合の数の総和と同じになる 

 

かしこい かしこい かしこい むかつく むかつく むかつく

 

 

 

 

解けなかった問題

ABC118C

今日のABCのC問題 無限にバグらせて通らなかった

一番最初に提出したのは、リストをソートして最小のやつで割り切れなかったら1を、割り切れたらリストの最小を返すやつ 3つ通らないのがあり再検討 

のちに18,22,20,22みたいな組では2が答えになることから、リストの中の最小値と他を比べて最大公約数を持って来ればよいことに気づく、も時間が足りず惨敗

 

ユークリッドの互除法を使う x,yに対し最大公約数を求めるアルゴリズムは、

def gcd(x,y):

  while y != 0:

    k = x

    x = y

    y = k%y

  return  x

とする

今回は、隣り合う数の最大公約数をユークリッドの互除法アルゴリズムを用いて、候補と比較してより小さいものに更新し続け、最終的に最も小さいものを答えとして返せばよい

悔しい