米田氏が企画した「競プロ典型90問」を解いていきます。
ここでは私の試行錯誤やら解説の理解やら気づきやらをメモしていこうと思います。
競プロ初心者なのでどうか温かい目で見守ってください。
なお、本記事中のPythonコードはブラウザ上で実行可能です。
よろしければ遊んでいってください。
問題
kyopro_educational_90/004.jpg at main · E869120/kyopro_educational_90
004 - Cross Sum(★2)
やってみよう!
テストデータ・テスト関数定義 ↓
# 縮小表示
test_data = [
{
"in":[3, 3, [[1, 1, 1], [1, 1, 1], [1, 1, 1]]],
"out": '''5 5 5
5 5 5
5 5 5
'''
},{
"in":[4, 4, [[3, 1, 4, 1], [5, 9, 2, 6], [5, 3, 5, 8], [9, 7, 9, 3]]],
"out": '''28 28 25 26
39 33 40 34
38 38 36 31
41 41 39 43
'''
},{
"in":[2, 10, [[31, 41, 59, 26, 53, 58, 97, 93, 23, 84], [62, 64, 33, 83, 27, 95, 2, 88, 41, 97]]],
"out": '''627 629 598 648 592 660 567 653 606 662
623 633 651 618 645 650 689 685 615 676
'''
},{
"in":[10, 10, [[83, 86, 77, 65, 93, 85, 86, 92, 99, 71], [62, 77, 90, 59, 63, 76, 90, 76, 72, 86], [61, 68, 67, 79, 82, 80, 62, 73, 67, 85], [79, 52, 72, 58, 69, 67, 93, 56, 61, 92], [79, 73, 71, 69, 84, 87, 98, 74, 65, 70], [63, 76, 91, 80, 56, 73, 62, 70, 96, 81], [55, 75, 84, 77, 86, 55, 96, 79, 63, 57], [74, 95, 82, 95, 64, 67, 84, 64, 93, 50], [87, 58, 76, 78, 88, 84, 53, 51, 54, 99], [82, 60, 76, 68, 89, 62, 76, 86, 94, 89]]],
"out": '''1479 1471 1546 1500 1518 1488 1551 1466 1502 1546
1414 1394 1447 1420 1462 1411 1461 1396 1443 1445
1388 1376 1443 1373 1416 1380 1462 1372 1421 1419
1345 1367 1413 1369 1404 1368 1406 1364 1402 1387
1416 1417 1485 1429 1460 1419 1472 1417 1469 1480
1410 1392 1443 1396 1466 1411 1486 1399 1416 1447
1397 1372 1429 1378 1415 1408 1431 1369 1428 1450
1419 1393 1472 1401 1478 1437 1484 1425 1439 1498
1366 1390 1438 1378 1414 1380 1475 1398 1438 1409
1425 1442 1492 1442 1467 1456 1506 1417 1452 1473
'''
},
]
def test_all(f):
for i, data in enumerate(test_data):
exp = data["out"]
ans = f(*data["in"])
result = "AC" if exp == ans else "WA"
print(f"{i+1} {result}")
下のmain0
関数を完成させて「Run」ボタンをクリックしよう!
def main0(H, W, A):
pass
test_all(main0)
自由欄
try(1st.)
このような単純に計算する問題はnumpy
でサクッと解きたいところです。
AtCoderのPythonでは以下ライブラリが使用可能です。(ref. Language Test 202001 - AtCoder)
- numba
- numpy
- scipy
- scikit-learn
- networkx
import numpy as np
def main(H, W, A):
data = np.array(A)
rows = data.sum(axis=1)
cols = data.sum(axis=0)
ans = np.add(*np.meshgrid(rows, cols, indexing="ij")) - data
ret = ""
for l in ans:
ret += f'{" ".join(l.astype(str))}\n'
return ret
test_all(main)
結果(1st.)
一発AC!
提出 #32460167 - 競プロ典型 90 問
他の人のコードを見てみる
せっかくなのでPythonで実装された他のコードを短いものから見てみましょう。
※類似するコードは提出の古いものだけ紹介します
"Python, 短い順" | すべての提出 - 競プロ典型 90 問
提出 #21938944 - 競プロ典型 90 問
_,*e=[[*map(int,s.split())]for s in open(0)]
*w,=map(sum,zip(*e))
for a in e:h=sum(a);print(*[h+w-a for a,w in zip(a,w)])
open(0)について
初見です。
pythonに慣れてくるほど思考停止してwith open()
と書いてしまいがちですが、結局with
構文は前処理と後処理を自動化してくれるだけなので、仕組みを理解していれば必ずしも必要ありません。
そしてopen
関数の場合の後処理は、f.close()
です。(ref. 7. 入力と出力 — Python 3.9.12 ドキュメント
もし with
キーワードを使用しない場合は、ファイルを閉じ、このファイルのために利用されたシステムのリソースを直ちに解放するために f.close()
を呼び出してください。
警告 f.write()
を with
キーワードや f.close()
を使わずに呼び出した場合、プログラムが正常に終了した場合でも、 f.write()
の実引数がディスクに完全に 書き込まれないことがあります 。
しかも上の引用の通り、問題が生じる可能性が高いのは書き込みの場合です。
もちろん実プロダクトであれば"安全な"コーディングをするに越したことはありませんが、競プロで使い捨てるレベルであればあまり神経質にならずともよさそうです。
本題の引数について、open関数はpath-like objectだけでなくファイルディスクリプタの整数も取ることができます。
そしてSTDINのファイルディスクリプタは0です。(Man page of STDIN)
従ってopen(0)
で、標準入力からEOFまで読み込んでファイルオブジェクトとして返してくれるという訳です。
1行目:入力データの読み込み
読み込んだ標準入力の最初の行を読み捨て、2行目以降をe
に代入しています。
# データの準備
# _,*e=[[*map(int,s.split())]for s in open(0)] に相当
e = test_data[1]["in"][2]
e
2行目:列ごとの合計を計算
まずzip(*e)
で2次元の配列を転置することができます。(すごい!)
list(zip(*e))
列の配列にしたところで、列ごとに合計を計算します。
ここでmap関数が返すのはイテレーターですが、アンパックして代入することでコーディング量を減らしています。(すごい!)
# mapが返すのはイテレーター
w = map(sum,zip(*e))
print(w)
# イテレーターからリストにする
print(list(w))
# アンパックでリスト化
*w, = map(sum,zip(*e))
w
# カンマが無いとエラー
*w = map(sum,zip(*e))
w
3行目:行の合計を計算しつつ、「行の合計」+「列の合計」を出力
for a in e:h=sum(a);print(*[h+w-a for a,w in zip(a,w)])
同じ変数名を使いまわしているのでこんがらがりそうですが、以下のことをやっています。
print
に複数引数を渡すとスペース区切りで出力されることをうまく利用しています。(天才か!)
col_sums = w
for row in e:
row_sum = sum(row)
print(*[row_sum + col_sum - r for r, col_sum in zip(row, col_sums)])
提出 #22302875 - 競プロ典型 90 問
h,w,*A=map(int,open(0).read().split())
C=[sum(A[i::w])for i in range(w)]
for i in range(h*w):
if i%w<1:r=sum(A[i:i+w])
print(r+C[i%w]-A[i])
1行目
こちらでは入力を1次元の配列として受け取っています。
# データの準備
from itertools import chain
# h,w,*A=map(int,open(0).read().split()) に相当
h,w,A = test_data[1]["in"]
*A, = chain.from_iterable(A)
h, w, A
2, 4行目
スライスを巧みに駆使して行と列の合計を計算しています。
(出力が指定と異なるのになぜかAC…)
C=[sum(A[i::w])for i in range(w)]
for i in range(h*w):
if i%w<1:r=sum(A[i:i+w])
print(r+C[i%w]-A[i])
提出 #24216523 - 競プロ典型 90 問
from numpy import*
print(*(sum(a:=int_([int_(t.split())for t in[*open(0)][1:]]),0)+vstack(sum(a,1))-a).flatten().tolist())
まさかのワンライナー!
やってることは以下と同等です。
A = test_data[1]["in"][2]
A
rows = np.sum(A, 1).reshape(-1, 1)
cols = np.sum(A, 0)
from pprint import *
pp(rows)
pp(cols)
pp(rows + cols - A)
行ベクトルと列ベクトルを普通に足したら行列を作れるんですね。
わざわざmeshgrid
使わなくてよかったんや…
まとめ
別にnumpy
使わずともサクッと書ける人は書ける😓
自由欄