JOI2012予選E イルミネーション

この記事で使うアルゴリズム

幅優先探索

はじめに

カテゴリー競プロ初中級者100問では、Qiitaにて@e869120さんがレッドコーダーが教える、競プロ・AtCoder上達のガイドライン【中級編:目指せ水色コーダー!】としてまとめられている100問をPythonで解いています。

全問題の一覧はこちらです

問題

https://atcoder.jp/contests/joi2012yo/tasks/joi2012yo_e

この問題は幅優先探索を用いて解くことができます。

方針

  • 周囲を1マス分だけ0でパディングします。建物がない箇所を幅優先探索し、建物にぶつかるたびにカウンタをインクリメントすることで、解を求めることができます。
  • 六角形の配置となっておりやや扱いにくいですが、隣接するマスを工夫して扱うことで二次元配列として表現できます。

    コード

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    import sys
    import queue
    dxdy_odd = ((-1,0), (1,0), (0,-1), (0,1), (1,-1), (1,1)) # タプルやリストで持っておくと便利
    dxdy_even = ((-1,0), (1,0), (0,-1), (0,1), (-1,-1), (-1,1)) # タプルやリストで持っておくと便利
    W, H = map(int,input().split())

    mp = []
    mp.append([0]*(W+2))
    for h in range(H):
    s = list(map(int, input().split()))
    mp.append([0]+s+[0])
    mp.append([0]*(W+2))

    visited = [ [0]*(W+2) for _ in range(H+2)]

    ans = 0
    q = queue.Queue()
    q.put((0,0)) # (0,0)は建物がないので、スタート地点にする
    while(not q.empty()):
    y, x = q.get()
    if visited[y][x] == 1: # 訪問済みの場合は無視する
    continue
    else:
    visited[y][x] = 1 # 訪問フラグを立てる
    if y%2 == 0:
    dxdy = dxdy_even
    else:
    dxdy = dxdy_odd
    for dx, dy in dxdy:
    if (0<= x+dx < W+2) and (0<= y+dy < H+2): # 範囲内に収まっているか
    if mp[y+dy][x+dx]==1:
    ans += 1
    else:
    if visited[y+dy][x+dx]==0:
    q.put((y+dy,x+dx))
    print (ans)

記事情報

  • 投稿日:2020年6月6日
  • 最終更新日:2020年6月6日

DDCC2020予選D Digit Sum Replace

はじめに

カテゴリー競プロ初中級者100問では、Qiitaにて@e869120さんがレッドコーダーが教える、競プロ・AtCoder上達のガイドライン【中級編:目指せ水色コーダー!】としてまとめられている100問をPythonで解いています。

全問題の一覧はこちらです

問題

https://atcoder.jp/contests/ddcc2020-qual/tasks/ddcc2020_qual_d

方針

実際にて計算をしてみると、どのような順で予選ラウンドを開催しても開催の回数が変わらないと分かります。

ただし、愚直に先頭からシミュレーションを行うと処理が間に合わないため、計算量を削減する方法を考えます。

予選を行うことで、桁数、全体の和がどのように変化するかに着目します。

  • ある予選ラウンドの和が10以上の場合
    桁数は変わらない。全体の和は9減る。
  • そうでない場合
    桁数が1減る。全体の和は変わらない。

これらの情報を元に、全体の和を一桁にするために何度、予選ラウンドを開催すれば良いかを計算できます。

コード

解法がわかりさえすれば、実装はとてもシンプルです。

1
2
3
4
5
6
7
8
M = int(input())
S = 0 # 全体の和
C = 0 # 桁数
for m in range(M):
d, c= map(int, input().split())
S += d*c
C += c
print ((C-1)+(S-1)//9)

記事情報

  • 投稿日:2020年6月5日
  • 最終更新日:2020年6月5日

三井住友信託銀行プログラミングコンテスト2019E Colorful Hats 2

はじめに

カテゴリー競プロ初中級者100問では、Qiitaにて@e869120さんがレッドコーダーが教える、競プロ・AtCoder上達のガイドライン【中級編:目指せ水色コーダー!】としてまとめられている100問をPythonで解いています。

全問題の一覧はこちらです

問題

https://atcoder.jp/contests/sumitrust2019/tasks/sumitb2019_e

方針

Aを先頭から順番に色を割り当てていきます。この時、それぞれの色が割り当てられている人数を保持しきます。

複数の色に割り当てられる人がいますが、この場合は割り当てられる色のうち任意の色としてしまっても一般性を失いません。ただし、割り当て可能な人数を解(組み合わせ)にかけています。

コード

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
N = int(input())
A = list(map(int, input().split()))
MOD = 1000000007
cnt = [0,0,0] # 現在割り当てられている人数
ans = 1 # 現時点での組み合わせ
for a in A:
idx = []
for i in range(3):
if cnt[i] == a: # 割り当て可能
idx.append(i)
if len(idx)==0: # 割り当て不能
print(0)
exit()
cnt[idx[0]]+=1 # 任意の色で良いので、一番番号が若い色とする
ans *= len(idx) # 割り当て可能な人数をかける
ans %= MOD
print (ans)

記事情報

  • 投稿日:2020年6月4日
  • 最終更新日:2020年6月4日

Tenka1 Programmer Beginner Contest 2018D Crossing

はじめに

カテゴリー競プロ初中級者100問では、Qiitaにて@e869120さんがレッドコーダーが教える、競プロ・AtCoder上達のガイドライン【中級編:目指せ水色コーダー!】としてまとめられている100問をPythonで解いています。

全問題の一覧はこちらです

問題

https://atcoder.jp/contests/tenka1-2018-beginner/tasks/tenka1_2018_d

この問題は深さ優先探索を用いて解くことができます。

方針

Nが、1からiまでの和として表せるような数であれば、要素数ii+1組の集合で実現できます。

コード

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
N = int(input())

i = 1
K = []
while(1):
k = i * (i+1) // 2
if k > 10**5:
break
K.append(k)
i+=1
if not N in K:
print ('No')
exit()
print ('Yes')
S = [[],[]]
i = 0

for n in range(1,N+1):
S[i].append(n)
S[-1].append(n)
i+=1
if n in K:
if n==N:
break
i = 0
S.append([])
print(len(S))
for s in S:
print(str(len(s)) + ' ' + ' '.join(map(str,s)))

記事情報

  • 投稿日:2020年6月3日
  • 最終更新日:2020年6月3日

JOI2009予選D 薄氷渡り

この記事で使うアルゴリズム

深さ優先探索

はじめに

カテゴリー競プロ初中級者100問では、Qiitaにて@e869120さんがレッドコーダーが教える、競プロ・AtCoder上達のガイドライン【中級編:目指せ水色コーダー!】としてまとめられている100問をPythonで解いています。

全問題の一覧はこちらです

問題

https://atcoder.jp/contests/joi2008yo/tasks/joi2008yo_f

この問題は動的計画法を用いて解くことができます。

ポイント

  • 動的計画法は再帰によって実装できます。
  • 配列の周囲を0で埋めると、配列外参照かを判定する条件分岐を書かなくてよく、実装が楽になる場合があります。
  • 再帰関数の最初に、mp[n][m] = 0として同じパス内で同じ区画を再度訪問しないようにします。関数から抜ける際に、mp[n][m] = 1としてそれを解除します。
    • 異なるパスでは再度訪問しても問題がないためです。

      コード

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
import sys
sys.setrecursionlimit(10**7)
dxdy = ((0,1),(0,-1),(1,0),(-1,0))
M = int(input())
N = int(input())

mp = []
mp.append([0]*(M+2))
for n in range(N):
line = list(map(int, input().split()))
mp.append([0]+line+[0])
mp.append([0]*(M+2))

ans = 0
def dfs(n,m,d):
global ans # グローバル変数
if mp[n][m] == 0:
return
if ans < d:
ans = d

mp[n][m] = 0
for dx, dy in dxdy:
next_n = n + dy
next_m = m + dx
dfs(next_n, next_m, d+1)
mp[n][m] = 1

for n in range(1,N+1):
for m in range(1,M+1):
dfs(n,m,1)
print (ans)

記事情報

  • 投稿日:2020年6月2日
  • 最終更新日:2020年6月22日

AOJ1160 島はいくつある?

この記事で使うアルゴリズム

深さ優先探索

はじめに

カテゴリー競プロ初中級者100問では、Qiitaにて@e869120さんがレッドコーダーが教える、競プロ・AtCoder上達のガイドライン【中級編:目指せ水色コーダー!】としてまとめられている100問をPythonで解いています。

全問題の一覧はこちらです

問題

http://judge.u-aizu.ac.jp/onlinejudge/description.jsp?id=GRL_1_C&lang=ja

この問題は深さ優先探索を用いて解くことができます。

解説

深さ優先探索は再帰で実装することができます。Pythonのデフォルト値だと小さすぎるので、再帰処理の上限を引き上げます。

コード

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
import sys
sys.setrecursionlimit(10**7)

dxdy = ((0,1),(0,-1),(1,0),(-1,0),(-1,1),(1,-1),(1,1),(-1,-1))
ans = []
while(1):
W, H = map(int, input().split())
if (W==0 and H==0):
break
mp = []
for h in range(H):
line = input()
line = line.split()
line = [-int(l) for l in line]
mp.append(line)
def dfs(h,w):
mp[h][w] = k
for dx, dy in dxdy:
nw = w + dx
nh = h + dy
if (0 <= nh < H and 0 <= nw < W):
if (mp[nh][nw] == -1):
dfs(nh, nw)
k = 0
for h in range(H):
for w in range(W):
if mp[h][w] == -1:
k += 1
dfs(h,w)
ans.append(k)
for a in ans:
print(a)

記事情報

  • 投稿日:2020年6月1日
  • 最終更新日:2020年6月1日

ABC169

はじめに

この記事では、AtCoder Beginer Contest 169のA~F問題を解説します。

A - Multiplication 1

https://atcoder.jp/contests/abc169/tasks/abc169_a

1
2
a, b = map(int, input().split())
print(a*b)

B - Multiplication 2

https://atcoder.jp/contests/abc169/tasks/abc169_b

整数の中に0が含まれる場合は答えが無条件で0となります。

先頭から積を計算していき、10**18を超えた段階で-1を出力すれば良いです。Pythonの場合、多倍長整数を扱えるのでサイズを気にしなくて良いように思えますが、巨大な数の計算は遅くなるため、最後まで計算しようとしてしまうとTLEとなります。

1
2
3
4
5
6
7
8
9
10
11
12
13
N = int(input())
A = list(map(int, input().split()))
INF = 10**18
if 0 in A:
print(0)
exit()
ans = 1
for a in A:
ans*=a
if ans > INF:
print(-1)
exit()
print (ans)

C - Multiplication 3

https://atcoder.jp/contests/abc169/tasks/abc169_c

浮動小数点数の誤差に気を付ける必要があります。

解法1

十進の浮動小数点数による計算を正確に行いたい場合は標準ライブラリのdecimalを用いると良いです。

1
2
3
4
from decimal import *
a, b = input().split()
ans = Decimal(a) * Decimal(b)
print (int(ans))

解法2

文字列の置換によりbを100倍することができます。

1
2
3
4
5
a, b = input().split()
b = b.replace('.','')
ans = int(a) * int(b)
ans = ans//100
print (int(ans))

解法3

floatで100倍してからintに変換します。ただし、愚直に実装すると誤差でわずかに意図した値より小さくなる場合があります。

1
2
3
b=9.95
print (int(float(b)*100))
print(float(b*100))
1
2
994.9999999999999
994

そこで微小な値を足します。ここでは0.5とします。

1
2
3
4
a, b = input().split()
ans = int(a)*int(float(b)*100+0.5)
ans = ans//100
print(ans)

D - Div Game

https://atcoder.jp/contests/abc169/tasks/abc169_d

素因数分解を行います。素因数分解がp1^e1 * p2^e2 * ...のようになるとして、各pi^eiについて、zの候補として小さい順すなわちpi^1, pi^2, pi^3,...の順に割ることができるかを確認していけば良いです。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
N = int(input())
def factorize(N):
n = N
r = []
for i in range(2,int(N**0.5)+1): # intに変換すること
cnt = 0
while(n%i==0):
cnt += 1
n //= i # 整数除算(//)を使うこと
if cnt!=0:
r.append((i,cnt))
if n!=1: # root(N)以上の素数がある場合
r.append((n,1))
return r

ans = 0
for _, cnt in factorize(N):
i = 1
while(i <= cnt):
cnt-=i
i+=1
ans+=1
print (ans)

E - Count Median

https://atcoder.jp/contests/abc169/tasks/abc169_e

Aの中央値M_aXの中央値の最小値、Bの中央値M_bXの中央値の最大値であることが分かります。

  • Nが奇数の場合
    • 中央値は区間[M_a,M_b]の全ての整数となります。すなわち、中央値として考えられるのはM_b-M_a+1個です。
  • Nが偶数の場合
    • 中央値は区間[M_a,M_b]のうち0.5刻みの実数となります。すなわち、中央値として考えられるのは2*(M_b-M_a)+1個です。
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      N = int(input())
      A = []
      B = []
      for n in range(N):
      a,b = map(int,input().split())
      A.append(a)
      B.append(b)

      A.sort()
      B.sort()

      if N%2==1:
      print (B[N//2]-A[N//2]+1)
      else:
      b = B[N//2]+B[N//2-1] # 中央値はこれを2で割るがあとで2を掛けるので相殺する
      a = A[N//2]+A[N//2-1]
      print (b-a+1)

F - Knapsack for All Subsets

https://atcoder.jp/contests/abc169/tasks/abc169_f

dp[i][s]1~i番目までの要素を使って構成しうる集合に対して、任意の要素を選択した際に和がsとなるような場合の数とします。

dp漸化式に登場する*2は、 A[i]を集合に加えるかどうかで分岐し場合の数が2倍になることに対応しています。

この実装ではPythonではTLEとなったため、PyPyを用いました。

1
2
3
4
5
6
7
8
9
10
11
12
N, S = map(int, input().split())
A = list(map(int, input().split()))
MOD = 998244353
dp = [[0]*(S+1) for _ in range(N+1)]
dp[0][0] = 1
for i in range(N):
for s in range(S+1):
if s-A[i] >= 0:
dp[i+1][s] = (dp[i][s] * 2 + dp[i][s-A[i]]) % MOD
else:
dp[i+1][s] = dp[i][s] * 2 % MOD # A[i]を集合に加えるかどうかで分岐するので、場合の数は2倍になる
print(dp[N][S])

関連記事

過去のABC解説記事です。

  • ABC168
    • A-E問題を解いています。
  • ABC167
    • A-E問題を解いています。
  • ABC166
    • A-F問題を解いています。
  • ABC165
    • A-F問題を解いています。
  • ABC164
    • A-E問題を解いています。
  • ABC163
    • A-D問題を解いています。
  • ABC162
    • A-E問題を解いています。

記事情報

  • 投稿日:2020年5月31日
  • 最終更新日:2020年6月13日

GRL1C 全点対間最短経路

この記事で使うアルゴリズム

ワーシャルフロイド法

はじめに

カテゴリー競プロ初中級者100問では、Qiitaにて@e869120さんがレッドコーダーが教える、競プロ・AtCoder上達のガイドライン【中級編:目指せ水色コーダー!】としてまとめられている100問をPythonで解いています。

全問題の一覧はこちらです

問題

http://judge.u-aizu.ac.jp/onlinejudge/description.jsp?id=GRL_1_C&lang=ja

この問題はワーシャルフロイド法を用いて解くことができます。

解説

このような問題は最短経路問題の中でも全点対最短経路問題と言います。

解法としては

  • ダイクストラ法をN回適用する
  • ワーシャルフロイド法
    などが考えられます。

ただし、負の重みがある場合はダイクストラ法を適用することはできません。そのため、今回はワーシャルフロイド法を用います。負の閉路がある場合、ワーシャルフロイド法の計算の結果、自身への最短距離dist[n][n]が負の値となります。

コード

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
N, M = map(int, input().split())
INF = float('inf')
cost = [[INF]*N for _ in range(N)]
for n in range(N):
cost[n][n] = 0
for m in range(M):
a, b, t = map(int, input().split())
cost[a][b] = t

for i in range(N): # 中継点
for j in range(N): # 始点
for k in range(N): # 終点
cost[j][k] = min(cost[j][i]+cost[i][k], cost[j][k])

for n in range(N):
if cost[n][n] < 0:
print ('NEGATIVE CYCLE')
exit()

for c in cost:
c = [str(i).replace('inf','INF') for i in c]
print(' '.join(c))

記事情報

  • 投稿日:2020年5月31日
  • 最終更新日:2020年5月31日

JOI2011予選E チーズ

この記事で使うアルゴリズム

幅優先探索

はじめに

カテゴリー競プロ初中級者100問では、Qiitaにて@e869120さんがレッドコーダーが教える、競プロ・AtCoder上達のガイドライン【中級編:目指せ水色コーダー!】としてまとめられている100問をPythonで解いています。

全問題の一覧はこちらです

問題

https://atcoder.jp/contests/joi2011yo/tasks/joi2011yo_e

この問題は幅優先探索で解くことができます。

方針

工場から次の工場への距離を幅優先探索で最小コストを計算する操作を繰り返せば良いです。計算量はO(NHW)となります。

コード

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
from collections import deque
dxdy = ((-1,0), (1,0), (0,-1), (0,1)) # タプルやリストで持っておくと便利
H, W, N = map(int,input().split())

maze = []

for h in range(H):
s = input()
maze.append(s) # 二次元リストにせず、文字列のリストのままでOK


def solve(sx ,sy, goal):
dist = [ [-1]*W for _ in range(H)]
dist[sy][sx] = 0
q = deque()
q.append((sy,sx)) # スタート地点をenqueue
while(q):
y, x = q.popleft()
if maze[y][x] == goal: # ゴールにたどり着いたら終了
break
else:
for dx, dy in dxdy:
if (0<= x+dx < W) and (0<= y+dy < H) and dist[y+dy][x+dx] == -1 and maze[y+dy][x+dx]!='X': # 見訪問かつ通行可能か
q.append((y+dy,x+dx))
dist[y+dy][x+dx] = dist[y][x] + 1 # 距離を記録
return x,y,dist[y][x]

for i in range(H):
for j in range(W):
if maze[i][j]=='S':
sx = j
sy = i
ans = 0
for n in range(1,N+1):
sx, sy, d = solve(sx, sy, str(n))
ans += d
print (ans)

記事情報

  • 投稿日:2020年5月30日
  • 最終更新日:2020年5月30日

square869120Contest#4B - Buildings are Colorful!

この記事で使うアルゴリズム

全探索

はじめに

カテゴリー競プロ初中級者100問では、Qiitaにて@e869120さんがレッドコーダーが教える、競プロ・AtCoder上達のガイドライン【中級編:目指せ水色コーダー!】としてまとめられている100問をPythonで解いています。

全問題の一覧はこちらです

問題

https://atcoder.jp/contests/s8pc-4/tasks/s8pc_4_b

方針

全探索によってどの建物が見えるようにするかを決めて、必要な費用を計算します。ある建物が見える条件は、それより左にある建物の高さの最大値より1大きいことなので、左から線形に最大値を更新していけば良いです。

計算量は高々N_C_KのパターンについてO(N)の操作を行えば良いので、十分間に合います。

コード

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
from itertools import combinations
N, K = map(int, input().split())
A = list(map(int, input().split()))
mn = 10**18
for B in combinations(range(1,N),K-1): # 一番左は固定
mx = A[0] # 初期値は一番左の建物
score = 0
for n in range(1,N):
if n in B: # 見えるようにすべき建物なら
if A[n] <= mx: # 低ければ高くする
mx += 1
score += (mx - A[n])
else: # 高さが十分なら何もしない。最大値を更新
mx = A[n]
else:
mx = max(mx, A[n]) # 見えなくても良い建物なら、最大値を更新
mn = min(mn, score)
print (mn)

記事情報

  • 投稿日:2020年5月29日
  • 最終更新日:2020年5月29日