備忘録

徒然なるままに。

競プロ典型:Score Sum Queries

うっかり投稿が途切れてしまった。
一先ず、解き切れていなくても書いたコードを上げていこうと思う。
考察等はモノによっては後日改めて投稿する形にして毎日コードをどんどん書いていこうと思う。継続は何とやら。

しばらくはAtCoderの競プロ典型を解いていく。

今日はScore Sum Queries。星2から解いていこうかと。この星は難易度を示しているという解釈でいいんだろうか?

自分のコード

N = int(input())
l = [list(map(int, input().split())) for l in range(N)]

Q = int(input())
q = [list(map(int, input().split())) for q in range(Q)]

for i in range(len(q)):
    min_num = q[i][0]
    max_num = q[i][1]
    
    new_list = l[min_num-1:max_num]

    score_first = 0
    score_second = 0
    
    for j in range(len(new_list)):
        if new_list[j][0] == 1:
            score_first += new_list[j][1]
        else:
            score_second += new_list[j][1]
    
    print(score_first,score_second)

脳筋コードである。ただでさえPythonでforを回すことは憚られるのに二重で回すもんだから勿論TLEを食らうわけである。一応テストはすべてクリアしている。
やっていること自体はそんなに難しいことではなくQを読み込みながらQの範囲に合わせてlistを組みなおしてクラスごとにスコアを足し合わせている。
このforの二重ループはどうやって解消すべきか今回は他の方々のコードから勉強したい。


コード1:

import sys
input = lambda: sys.stdin.readline().rstrip()
# sys.setrecursionlimit(10**6)
 
 
N = int(input())
class1 = []
class2 = []
for i in range(N):
    c, p = map(int, input().split())
    if c == 1:
        class1.append(p)
        class2.append(0)
    else:
        class1.append(0)
        class2.append(p)
 
cumsum1 = [0]
cumsum2 = [0]
for i in range(N):
    cumsum1.append(cumsum1[-1]+class1[i])
    cumsum2.append(cumsum2[-1]+class2[i])
 
Q = int(input())
for _ in range(Q):
    l, r = map(int, input().split())
    l -= 1
    print(cumsum1[r]-cumsum1[l], cumsum2[r]-cumsum2[l])

Submission #40072295 - 競プロ典型 90 問 より

コード2:

n=int(input())
 
s1=[0]
s2=[0]
t1=0
t2=0
 
for i in range(n):
    c,p=map(int,input().split())
    if c==1:
        t1+=p
    else:
        t2+=p
    s1.append(t1)
    s2.append(t2)
 
q=int(input())
for i in range(q):
    l,r=map(int,input().split())
    print(s1[r]-s1[l-1], s2[r]-s2[l-1])

Submission #40169579 - 競プロ典型 90 問より

直近提出されたコードからそれぞれ実行時間が最も短いモノとコード長が短いモノを引用させて頂いた。
そもそも冷静に考えて入力をとる段階から処理をすればここまでループがかさばらないんだった(いつかも似たような反省をした気がする。。)。
内包表記しているだけで標準入力をとっている部分もループしているのだった。これを分けずに処理をその中でもすることでより早くなりそうだ。

二つ引用したがやっていることは同じでクラスごとに一人ずつ得点の和を学籍番号順にリストに入れていって最後にQで指定された学籍番号の上下で差をとることで、指定された学籍番号内の生徒の得点の総和を出力している。

自分のコードと比較すると、自分の者にはやはりループ削減の意識と総和計算の工夫が足りないことが分かる。

競プロ典型:Cross Sum

WBCの余韻でしばらく更新していなかったので、少し前に書くだけ書いていたものを公開。回答の熟考等はまた明日以降改めて。

                  • -

行き詰ったので少し先にやろうと思っていた競プロ典型から題名の問題を。

自分のコード

H, W = map(int, input().split())

arr = [list(map(int, input().split())) for l in range(H)]

arr_sum_l = [sum(x) for x in zip(*arr)]
arr_sum_r = [sum(x) for x in arr]

for i in range(H):
  arr_ans = []
  for j in range(W):
    val = arr_sum_l[j] + arr_sum_r[i] - arr[i][j]
    arr_ans.append(val)
  print(*arr_ans)

for文の回し方にやや苦戦した。普通に読み込んで普通に処理しようとすると制限時間を越えてしまったので内包表記を使用してなんとか時間内に落とし込んだ。

参考コード

import sys
input = sys.stdin.readline
h, w = [int(i) for i in input().split()]
A = [[int(i) for i in input().split()] for _ in range(h)]
 
row_sums = [sum(a) for a in A]
column_sums = [0] * w
for a in A:
    for i in range(w):
        column_sums[i] += a[i]
 
res = [[row_sums[y] + column_sums[x] - A[y][x] for x in range(w)] for y in range(h)]
 
for r in res:
    print(*r)

余談:ゲームにおけるストレスの「有用性」

今回は趣味の話を少し。

ゲームというのは少し特殊なコンテンツで人間のストレスをコントロールすることで様々な効果を発揮する。
一般にストレスはかからなければかからないに越したことはないし何なら発散させてほしいものだと思う。趣味としてよく上がるであろう例えば旅行やライブ鑑賞、スポーツ観戦等々…何らかの理由でストレスがかかってしまうことはあっても基本的にはストレスは解消され得るものである。
ところがどっこい、ゲームはプレイヤーに適度のストレスをかけることで面白さを演出する。逆に言えばストレスの全くないゲームは面白くないという話である。

今回は私がよくプレイするポケモンSV(スカーレット・ヴァイオレット)を題材にゲームにおけるストレスの形態について考えていることを書き残そうと思う。
以下の論はあくまで主観に基づくものであって何らかの理論に基づいたものではない、1ゲーマーの感想だと思って温かい目で読んでいただきたい。

ポケモンは子供から大人まで幅広い年齢層の人々にプレイされるゲームだ。こんなゲームにストレス要素が?と思うかもしれないが明確に存在する。挙げ始めたらキリがないが軽く列挙すると
・対戦での勝ち負け
 ・急所負け(=運負け)
ポケモン捕獲(言い方がよくないな、ゲット?)
 ・色違い捕獲
 ・個体厳選
 ・レイドバトル(SVに限る)
ひとまずこんなところでいいか。
ポケモンにおいては大きく分けて対戦・捕獲(厳選)においてストレス要素が盛り込まれている。とりわけ現在のポケモンはストーリー攻略よりもオンライン対戦に比重が重く置かれているため対戦によるところが大きい。
対戦におけるストレスの働きはシンプルで勝てば発散、負ければストレス負荷である。(以下発散をマイナス、負荷がかかることをプラスとか呼ぶ)
捕獲は伝説ポケモンとか捕まえる時が分かりやすいか。ボール投げても捕まらずはじき返されるあの瞬間にプラス、捕まればマイナスといったところである。
ただゲームにおけるストレスのやり取りは勝ち負け・捕まる/らないのYes/Noの二択のみに依らず他にもいろいろな形でポイントが用意される。それがそれぞれの下にサブセクション的に書いたもので、例えば対戦では急所にあたりダメージが増すことが敗因となる急所負けなどがある。これらは乱数的に用意されることが多い。個体厳選なんかもその一つである。
ただし、勿論これらをストレスと認識するかは人それぞれである。負けても全くストレスを感じないメンタル鋼タイプな人がいてもおかしくはないし色違いを永遠に粘り乱数との果てなき戦いを繰り広げる人もいる。ただゲーム設計の段階である程度こうしたストレスを如何にコントロールするかは設定されているはずである。

ゲーマーとしてやっていて楽しいと感じるゲームはこうしたストレスが適切にかかり適切に発散されるゲームである。これはポケモンに限らす多くのゲームに言える。
これが分かりやすいのはエルデンリング(フロムゲー、4にげー)かもしれない。強い敵に何度も挑み負けても勝てるようになるまであれこれ試行錯誤する。数時間やっていると少しづつ相手の動きが見えるようになってきて糸口が見え始めてくる。倒せた時の爽快感は格別である。
これも敵の強さ=ストレスがかかってこそ倒した時の達成感が増していると言えよう。

先に述べたように何をストレスと感じるかもその閾値の加減も人それぞれではあるが、ゲームは閾値ギリギリまでストレスがたまりつつも閾値を超える直前でそれが発散されるその瞬間にこそ最大の爽快感を生む。あえてストレスをためることでそれが発散される瞬間により大きな価値をもたらす。上げて落とす戦法である。ジェットコースターみたいな。

ポケモン閾値ギリギリを責めるゲームではなく上げて落とすを繰り返すことで細かく爽快感をもたらすゲームで、エルデンリングはギリギリまでストレスをかけて一気に落とすことで大きな爽快感をもたらすゲームと言い換えられる。
後者の設計は非常に難しいだろう。ストレスの閾値が低い人にはまず受け入れられないからである。爽快感を経験する前にゲームを手放すだろう。これも一つ万人受けするかどうかのラインであり、基本的には前者のように細かく上げて落とすを繰り返すゲームが多い。ポケモン以外にもFPSゲームもこれに近いだろう。敵を倒せばマイナス、倒されればプラス。スプラトゥーンのように1試合で何回もリスポーンするようなゲームは1試合の中でこれを何度も繰り返すのである。

さて、ここまでゲームにおける「意図された」ストレスについて書き連ねた。ストレスはかからないに越したことはないがゲームにおいてはそれが更なる爽快感を与え効果的に働く場合がある。しかしゲームにおけるストレスとは必ずしも意図されたものばかりかかるわけではない。
例えばローディングが長い、画質が荒い、操作が直感的でない、バグが存在する等々である。何度も書き連ねているようにどれほどストレスを許容できるかは人によるがこれらは気になる人はとことん気になるものである。
今回、冒頭でなぜポケモンを取り上げたか。それはポケモン最新作がストーリーがよくできているがゆえにこうした意図されないストレスが目立ってしまっていると考えているためである。もっと端的に言うとただの愚痴である。
個人的にポケモン最新作で気になっているのは(修正済みも含む)対戦におけるカメラワークや技相性の表示関係、テラピース周り、ボックスの操作性、対戦画面までの導線の長さ辺りである。
ここでは最後に書いたことに触れる。ポケモンは他のRPGに比べてもストーリーはそこまで長くはない。またセーブデータも一つしかないため基本的には周回性がない。従ってプレイヤーはストーリーが終われば厳選か対戦を楽しむことになり、大多数の人がオンラインでの対戦・ランクマッチをプレイすることになると思う。むしろそのほうが全体的なプレイ時間としては多くなると思う。だが前作剣盾と比較しても気になるのがその対戦画面まで行くのがやや面倒なことだ。もしかしたら気づいていないショートカットでもあるかもしれないが前作に比べて1,2画面増えていてかつロードも長い。
そんなことで何を言っているのかと思われる方もいるだろうが個人的にはよく分からないしとても気になるのである。例えるならソシャゲで課金できるショップをわざわざ隠すようなものである。課金してガチャ引きたいのにどこで課金するかよく分からない、なんてことはソシャゲにとって死活問題である。要はストーリークリア後のメインコンテンツであるはずの対戦要素が表に出ていないのが違和感なのである。

こうした細かい違和感はゲームにとっては致命的になりかねない。この違和感は先に述べたストレスの一種となり得るのである。例えばバグ一つ存在しそれが修正されなければプレイするたびに快適なプレイを阻害するストレスが付きまとうのである。すると意図されたストレスのみの勘定では釣り合っていたのに余計なストレスがかかってしまい閾値を超えてしまうことが起こり得る。
これがCSゲーならまだしもソシャゲではもっと深刻で運営存続の危機を迎えかねない。しかも現在のゲームはどれも良く作られていて自由度も高く、さらに基本プレイ無料のゲームが浸透していてそうしたゲームに触れる機会も増えるためこうした意図されないストレスの存在は非常にシビアなものと言える。

今回はたまたまポケモンを例に挙げたがこうした事象はどんなゲームにも起こり得る。さらに正直自分としてはポケモンのこうした意図されないストレスをさほど気にしているわけではない。それは、恐らく読んで下さった皆さんが思っているように「嫌ならプレイしなければよい」からである。実際、現在は剣盾発売当初の同時期よりプレイ機会は控えめになっている(まぁこれは対戦画面云々じゃなくテラピースが原因なんだが)。ただ、私はそれでいいがゲームはそれでいいのだろうか。

実はこの文章はいつか自分がゲームを制作するときのための備忘として書いている。現在ソシャゲを見れば多くのゲームがリリースされてはサービス終了していく。それだけ競争は激しく少しでも落ち度の目立つゲームは見向きもされなくなっている。まさに「嫌ならやめればよい」からである。
ゲームを作る際にバグが生じるのは仕方のないことである。人間が作っているものなのだからエラーの一つや二つあっておかしくはない。だからこそUIやデザイン、ゲームバランスや設計段階で無駄なストレスがかかることは出来るだけ排除されなければならない。制作側の事情の押しつけはプレイヤーの知ったことではない。プレイヤー目線で適切なストレスコントロールが実現できているか、基本的なところを抑えられたし。そう願ってやまない。

ABC086C

otosidamaと白昼夢は考え中なのでいったん飛ばして今日はTravelingを解いた。
自分のコード

N = int(input())

N = N + 1

t = [0] * N
x = [0] * N
y = [0] * N
for i in range(N-1):
    t[i+1], x[i+1], y[i+1] = map(int, input().split())
    
for j in range(N-1):
    turn = t[j+1] - t[j]
    move = abs(x[j+1] - x[j]) + abs(y[j+1] - y[j])
    
    if turn < move:
        result = "No"
    elif turn == move:
        result = "Yes"
    elif turn > move:
        if turn % 2 == move % 2:
            result = "Yes"
        else:
            result = "No"

print(result)

少し丁寧に書き残しておく。
標準入力の読み込みの段階でやや変なことをしている。最初に読み込んだNに1加えている。
そのくせfor文内で1引いているので一見すると何しているんだという感じだがこれはt,x,yをリスト化する段階で初期状態の情報を加えるために入れている。(t,x,y)=(0,0,0)をリストに入れてしまおうというわけである。

標準入力後jに関して回しているfor文が本番である。
まずturnとmoveという計算をしている。これはそれぞれ移動時間と移動距離の絶対値を示す。どれだけの時刻移動したか、x,yについてどれだけ移動したかを意味する。
その次に条件分岐を行っている。まずは今計算したturnとmoveの大小関係を比較する。
turnがmoveより小さいとき、一致するときは明らかに前者は到達不可能で後者は到達可能である。
turnがmoveより大きいときは同じ道を行ったり来たりすることで到達可能な場合と不可能な場合が出てくる。これを2つの変数の偶奇によって判定している。

ただ、ここまで書いてこのコードに問題があるんじゃないかという気がしてきた。条件分岐後のresultの扱いがまずそうに感じる。これだとループ中にNo判定が出てもその後Yesの判定に上書きされる可能性がある。ただ、これでも一応AC通ったので恐らくテストケースにそういったものがたまたまなかったのかもしれない。そこで下のコードは一度TrueかFalseで格納し最後の出力を判定する方法に変えた。

N = int(input())

N = N + 1

t = [0] * N
x = [0] * N
y = [0] * N
for i in range(N-1):
    t[i+1], x[i+1], y[i+1] = map(int, input().split())
    
result = True
for j in range(N-1):
	turn = t[j+1] - t[j]
	move = abs(x[j+1] - x[j]) + abs(y[j+1] - y[j])
    
	if turn < move:
		result = False
	elif turn == move:
		result = True
	elif turn > move:
		if turn % 2 == move % 2:
			result = True
		else:
			result = False
    
	if result == False:
		break
     
if result:
    print("Yes")
else:
    print("No")

一番目のコードとは大した差はないが恐らくこちらの方が適切で勿論こちらもAC通った。
以下のコードは発想は同じだがまとまっている。

N = int(input())
 
t_old = 0
x_old = 0
y_old = 0
 
flag = True
 
for _ in range(N):
    t, x, y = map(int, input().split())
    dis = abs(x-x_old) + abs(y-y_old)
    t_sa = t - t_old
 
    if t_sa < dis or t_sa % 2 != dis % 2 :
        flag = False
 
    t_old = t
    x_old = x
    y_old = y
 
if flag:
    print('Yes')
 
else:
    print('No')

Submission #39789318 - AtCoder Beginners Selectionより

・最初の標準入力のループで処理しているためforが一回少ない
・条件もorでまとめられていて可読性が高い
点でよく見えるがこのループもやはり上書きの可能性を取り切れていないままAC通っているように思える。
そもそも前提として例えばケースとして与えられる座標が複数存在するときに途中の経路において到達不可能な点を通らないならforループ内で判定する必要もなくなる。
それすなわち原点→A→ゴールとしてA・ゴールが与えられているときに原点からAまでがそもそも到達不可能でAからゴールが到達可能な時にはYesが出力されるがこれは本来Noが出力されるべきではないんだろうか?という話である。

t0, x0, y0 = 0, 0, 0
for _ in range(int(input())):
  t, x, y = map(int, input().split())
  margin = (t-t0) - abs(x-x0) - abs(y-y0)
  if margin < 0 or margin%2 != 0:
    print("No")
    break
  t0, x0, y0 = t, x, y
else:
    print("Yes")

Submission #39725551 - AtCoder Beginners Selectionより。

これまたやっていることは同じだがよりコードがまとまったもの。ここではNo判定が出るとbreakするようになっている。
いろいろ眺めた結果、やはりテストケース的にたまたまACが出るだけでケアすべきだと感じる。

こういう条件分岐の問題は分岐させすぎてもコードとしてクオリティの高いモノにならないし、シンプルを追求しすぎても例外をケアできなかったりと中々思考が難しいものである。
が、発想さえできればコード化は比較的労せず出来るので思考力を鍛えていきたい。
それとPythonを使う以上、forを少なくすることは常に頭に入れておきたい。まだまだである。

ABC085B

自分のコード

N = int(input())
A = []
for _ in range(N):
    A.append(int(input()))
    
print(len(set(A)))

リストから重複した要素を取り除き、取り除いた後のオブジェクトの長さを表示させるもの。
この問題に関してはいろいろ眺めてもこれが最適かと。実行時間21ms、直近だと一番早そう(誤差の範囲だとは思うけど)。
可読性を考えればA_setを導入するとか出来るか。とは言えこれで十分だと思う。
setを使わないときは並べ替えたうえで前後同士値を比較して重複要素を取り除いたり…うーん、どう考えてもsetで良さそう。

しかしキチンとPythonの操作を主体にコードがパッと思いついたのでそこはよかった。プログラミング思考に少しずつ慣れてきた。

ちなみにsetだと適用後のオブジェクトは適用前から重複要素が削除されるだけじゃなく並びも変わるので注意が必要か。
これを回避し並びを保持するには

dict.fromkeys(arr)

が使えるらしい。辞書型は重複する要素を持ちえないことを使っている。なるほど。

ABS088B

以前解いた問題について。
自分のコード

import numpy as np

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

new_list = sorted(list)

A = 0
B = 0

for j in range(Num):
    if j%2 == 0:
        A += new_list[j]
    else:
        B += new_list[j]

print(np.abs(A-B))

入力された数列をソートしたうえで二人に交互に割り振っていく(A,Bそれぞれに交互に加算していく)。
これまた愚直に書いたもの。以下は根本は同じ思考だがコードがきれい。

N = int(input())
a = sorted(list(map(int,input().split())),reverse = True)
# a.sort(reverse = True)
Alice = a[0::2]#スライスで検索
Bob = a[1::2]
print(sum(Alice)-sum(Bob))

Submission #39746168 - AtCoder Beginners Selectionより引用

数列をスライスして分割している。どうも自分は数列を扱うのに不慣れらしい。算数的発想を実現する上で数列操作を有用に使えるようになりたい。
ところで自分のコードでは最後にabsを用いている(何ならこのためだけにnumpyを持ってきている)訳だが、ソートして大小関係がはっきりするなら不要そうに見える。調子に乗ったな、過去の自分(苦笑)。

ABS083B

AtCoder、ABS083Bを解いた。

自分のコード

N, A, B = map(int, input().split())
t = 10000

def val_sum(val,t):
    res = 0
    while t > 0:
        r = val // t
        val = val % t
        t = t // 10
        res += r
    
    return res

ans = 0

for i in range(N+1):
    if val_sum(i,t) >= A and val_sum(i,t) <= B:
        ans += i
        
print(ans)

関数内は与えられた数値の商を求めることで各桁の値を分解して和を求めている。
わざわざこんなことしなくても数列的に分解できるんだろうなぁと思いながら書いた。
実際にそれを再現しているコードが以下。

a, b, c = map(int, input().split())
count = 0
for ele in range(a + 1):
    digits = list(map(int, list(str(ele))))
    if b <= sum(digits) and sum(digits) <= c:
        count += ele
print(count)

Submission #39744452 - AtCoder Beginners Selectionより引用

for分の回し方は同じ。digitsの部分でeleを文字列的に分解してint形に直してlistに変換。そのsumを用いている。
標準入力を応用していて(応用もへったくれもないか)こちらの方がキレイ。

if A <= sum(list(map(int, str(i)))) <= B:

Submission #39742834 - AtCoder Beginners Selectionより引用

これが最もシンプルかな。やっていることは二つとも同じこと。

数値判定の時は算数的な発想だけじゃなく言語を生かした発想が大事だと思った。