Atcoder水色への精選 100 問 幅優先探索(Python)
水色になるための精選100問、
今回は28~33までの「幅優先探索」です
前回↓
深さ優先探索
algo-archit.hatenablog.com
目次
- 28 ALDS_11_C - 幅優先探索
- 29 AtCoder Beginner Contest 007 C - 幅優先探索
- 30 JOI 2011 予選 5 - チーズ
- 31 JOI 2012 予選 5 - イルミネーション
- 32 AOJ 1166 - 迷図と命ず
- 33 AtCoder Beginner Contest 088 D - Grid Repainting
28 ALDS_11_C - 幅優先探索
問題文
http://judge.u-aizu.ac.jp/onlinejudge/description.jsp?id=ALDS1_11_C&lang=ja
dequeと呼ばれる両端キューを用いることで問題を解くことができます
そのノードに訪問したか、そのノードまでの距離をそれぞれ格納するリストを用意しておき、
訪問済みでないノードにたどり着いた場合にキューを追加します
キューがすべてなくなった時の距離が答えです
pythonでの実装例
from collections import deque inf = 10**10 n = int(input()) network = [[] for _ in range(n+1)] length_list = [inf for _ in range(n+1)] visited_list = [0 for _ in range(n+1)] for _ in range(n): inputs = list(map(int,input().split())) u,k = inputs[:2] v = inputs[2:] network[u] += v que = deque() # キューを作成 que.append(1) length_list[1] = 0 while que: node = que.popleft() # 先頭から要素を取得 visited_list[node] = 1 for i in network[node]: if visited_list[i]: continue que.append(i) # visited_list[i] = 1 # 長さ+1をしても現在より短くなる場合は更新 if length_list[i] > length_list[node]+1: length_list[i] = length_list[node]+1 for i in range(1,n+1): if length_list[i] == inf: print(i,-1) else: print(i,length_list[i])
29 AtCoder Beginner Contest 007 C - 幅優先探索
問題文
atcoder.jp
マス目状の問題の場合でも二次元型のリストを使って同じように解くことができます
壁に当たらず、かつ訪問済みでないマスをキューに追加して答えのマスまでの距離を求めます
pythonでの実装例
from collections import deque inf = 10**10 R,C = map(int,input().split()) sy,sx = map(int,input().split()) gy,gx = map(int,input().split()) c = [input() for _ in range(R)] sy -= 1 sx -= 1 gy -= 1 gx -= 1 inf = 10**10 visited = [[0 for i in range(C)] for j in range(R)] length = [[inf for i in range(C)] for j in range(R)] que = deque([]) que.append([sy,sx]) visited[sy][sx] = 1 length[sy][sx] = 0 while que: y,x = que.popleft() for y_move,x_move in [[-1,0],[1,0],[0,-1],[0,1]]: ny = y+y_move nx = x+x_move if visited[ny][nx] or c[ny][nx] == "#": continue que.append([ny,nx]) visited[ny][nx] = 1 if length[ny][nx] > length[y][x]+1: length[ny][nx] = length[y][x]+1 print(length[gy][gx])
30 JOI 2011 予選 5 - チーズ
問題文
atcoder.jp
硬さ1のチーズから順番に食べていけばOKです
順番を辞書に格納しておき、各中継地点までのかかる時間(距離)を合計したものが答えとなります
pythonでの実装例
from collections import deque,defaultdict inf = 10**10 h,w,n = map(int,input().split()) town = [input() for _ in range(h)] factory = defaultdict(int) # スタートと工場の順番を辞書に入れる(スタートを0とする) for i in range(h): for j in range(w): if town[i][j] == "S": factory[0] = [i,j] elif town[i][j] == "X" or town[i][j] == ".": continue else: factory[int(town[i][j])] = [i,j] def bfs(sy,sx,gy,gx): visited = [[0 for j in range(w)]for i in range(h)] length = [[inf for j in range(w)]for i in range(h)] que = deque([]) que.append([sy,sx]) visited[sy][sx] = 1 length[sy][sx] = 0 while que: y,x = que.popleft() for i,j in [[-1,0],[0,1],[0,-1],[1,0]]: ny,nx = y+i,x+j if 0 <= ny < h and 0 <= nx < w and visited[ny][nx]== 0 and town[ny][nx] != "X": que.append([ny,nx]) visited[ny][nx] = 1 length[ny][nx] = length[y][x]+1 return length[gy][gx] total = 0 # 最終的にかかる時間 # 順番に経路をたどっていく for i in range(1,n+1): sy,sx = factory[i-1] gy,gx = factory[i] total += bfs(sy,sx,gy,gx) print(total)
31 JOI 2012 予選 5 - イルミネーション
問題文
atcoder.jp
やっかいな問題に見えますが、移動できる方向が4→6に増えたと考えれば大丈夫です(偶奇の時の場合分けに注意)
今回はマス目の外側に1つ分余分なマスを作り、自由に動き回ったときに、何回壁に当たったかをカウントします
このときのカウント数がイルミネーションする壁面の個数になります
この問題に関しては公式の解説図がとっっっても分かりやすかったので確認してみてください↓
www.ioi-jp.org
pythonでの実装例
from collections import deque inf = 10**10 w,h = map(int,input().split()) field = [[0 for j in range(w+2)]for i in range(h+2)] visited = [[0 for j in range(w+2)]for i in range(h+2)] for i in range(h): field[i+1][1:w+1] = map(int,input().split()) # どの行かで動き方が異なる odd_move = [[-1,0],[-1,1],[0,1],[1,1],[1,0],[0,-1]] even_move = [[-1,-1],[-1,0],[0,1],[1,0],[1,-1],[0,-1]] que = deque([]) que.append([0,0]) visited[0][0] = 1 # 壁にぶつかった回数をカウント collision = 0 while que: y,x = que.popleft() if y%2 == 1: move = odd_move else: move = even_move for i,j in move: ny,nx = y+i,x+j if ny < 0 or ny >= h+2 or nx < 0 or nx >= w+2: continue elif field[ny][nx] == 1: collision += 1 # 壁にぶつかったのでカウント continue elif visited[ny][nx]: continue else: que.append([ny,nx]) visited[ny][nx] = 1 print(collision)
32 AOJ 1166 - 迷図と命ず
問題文
http://judge.u-aizu.ac.jp/onlinejudge/description.jsp?id=1166
一見ややこしくてわかりにくい問題ですが、マス目を新しく自分で作り、
縦横の4方向に動くことができるかを壁のリストから参照します
端側の処理だけ注意していれば問題なく解くことができます
pythonでの実装例
from collections import deque inf = 10**10 # 0,0入力が来るまで while True: w,h = map(int,input().split()) if w == 0 and h == 0: break v_wall = [] # 垂直壁 h_wall = [] # 水平壁 for i in range(2*h-1): if i%2 == 0: v_wall.append(list(map(int,input().split()))) else: h_wall.append(list(map(int,input().split()))) visited = [[0 for i in range(w)] for j in range(h)] length = [[inf for i in range(w)] for j in range(h)] visited[0][0] = 1 length[0][0] = 1 que = deque([]) que.append([0,0]) while que: y,x = que.popleft() can_go = [] # 行くことができる方向 # 左確認 if x != 0 and v_wall[y][x-1] != 1: can_go.append([y,x-1]) # 右確認 if x != w-1 and v_wall[y][x] != 1: can_go.append([y,x+1]) # 上確認 if y != 0 and h_wall[y-1][x] != 1: can_go.append([y-1,x]) # 下確認 if y != h-1 and h_wall[y][x] != 1: can_go.append([y+1,x]) if not can_go: continue for ny,nx in can_go: if visited[ny][nx]: continue que.append([ny,nx]) visited[ny][nx] = 1 length[ny][nx] = length[y][x]+1 if length[h-1][w-1] != inf: print(length[h-1][w-1]) else: print(0)
33 AtCoder Beginner Contest 088 D - Grid Repainting
問題文
atcoder.jp
問題文を言い換えると
「最短距離のマス目以外を黒くするにはあと何マス塗りつぶせばいいですか?」
となります
よって答えは、
「全マスの個数H*W - 最短距離 - すでに黒いマスの個数」
です
pythonでの実装例
from collections import deque inf = 10**10 h,w = map(int,input().split()) field = [input() for _ in range(h)] length = [[inf for j in range(w)]for i in range(h)] visited = [[0 for j in range(w)]for i in range(h)] move = [[-1,0],[1,0],[0,-1],[0,1]] que = deque([]) que.append([0,0]) length[0][0] = 1 # 1スタート visited[0][0] = 1 white = 0 for i in range(h): white += field[i].count(".") while que: y,x = que.popleft() for i,j in move: ny,nx = y+i,x+j if ny < 0 or ny >= h or nx < 0 or nx >= w: continue elif field[ny][nx] == "#": continue elif visited[ny][nx] == 1: continue visited[ny][nx] = 1 que.append([ny,nx]) length[ny][nx] = length[y][x] + 1 if length[h-1][w-1] == inf: print(-1) else: print(white-length[h-1][w-1]) # 白の総数から最小距離分引く
Atcoder水色への精選 100 問 深さ優先探索(Python)
水色になるための精選100問、
今回は24~27までの「深さ優先探索」です
再帰は前から苦手だったので理解するいい機会になりました
前回↓
二分探索
algo-archit.hatenablog.com
目次
- 24 ALDS_11_B - 深さ優先探索
- 25 AOJ 1160 - 島はいくつある?
- 26 AtCoder Beginner Contest 138 D - Ki
- 27 JOI 2009 予選 4 - 薄氷渡り
24 ALDS_11_B - 深さ優先探索
問題文
http://judge.u-aizu.ac.jp/onlinejudge/description.jsp?id=ALDS1_11_B
深さ優先探索では、目的のノードをみつけるか、探索できる子ノードがなくなるまで探索を行っていきます
スタックを使った方法や再帰関数を用いた解き方ができます
この問題ではたどり着いた時間だけでなくそのノードについて一通り調べ終わった順番まで記録しなければいけなかったので再帰的な手法で解きました
ネットワークとして全てのノードがつながっているとは限らない点に注意です
pythonでの実装例
n = int(input()) v_list = [[] for _ in range(n+1)] for _ in range(n): li = list(map(int,input().split())) u,k = li[:2] v = li[2:] v.sort() v_list[u] += v inf = 10**8 d = [inf for _ in range(n+1)] f = [inf for _ in range(n+1)] time = 1 def dfs(now): global time # 探索済みだった場合中断 if d[now] != inf: return d[now] = time time += 1 for i in v_list[now]: dfs(i) f[now] = time time +=1 # 1番目から探索を開始(孤立ノードに注意) for i in range(1,n+1): dfs(i) print(i,d[i],f[i])
25 AOJ 1160 - 島はいくつある?
問題文
http://judge.u-aizu.ac.jp/onlinejudge/description.jsp?id=1160
まずW*H個のすべてのマスについて1つづつ探索を行います
未だ探索されておらず、かつ島のマスがあった場合、そのマスを始点として深さ優先探索を隣接する8マスに対して行います
また島の個数を1カウント増やします
この際、探索をし終わった島のマスを海のマスに変更することで重複して島をカウントするのを防ぐことができます
pythonでの実装例
while True: w,h = map(int,input().split()) if w == 0 and h == 0: break c = [list(map(int,input().split())) for _ in range(h)] visited = [[0 for i in range(w)]for j in range(h)] # 訪問したか確認 island = 0 # 島の個数 xy_move = [] # 8方向のパターン for i in range(-1,2): for j in range(-1,2): if not(i == 0 and j == 0): xy_move.append([i,j]) # 全てのマスに関して探索 for i in range(w): for j in range(h): # 海or探索済みだった場合打ち切り if visited[j][i] == 1 or c[j][i] == 0: continue stack = [[i,j]] island += 1 while stack: x,y = stack.pop() c[y][x] = 0 # 自身は塗りつぶす for k in xy_move: x_move,y_move = k nx = x+x_move ny = y+y_move if 0 <= nx < w and 0 <= ny < h and c[ny][nx] == 1 and visited[ny][nx] == 0: visited[ny][nx] = 1 c[ny][nx] = 0 # 自身は塗りつぶす stack.append([nx,ny]) print(island)
26 AtCoder Beginner Contest 138 D - Ki
問題文
atcoder.jp
「各 i (1 ≤ i ≤ N −1)に対して頂点 ai が bi の親であるとは限らない」(解説引用)ため、再帰を使わない深さ優先探索では問題を解くことができません(コンテスト本番中はこの制限に引っかかるテストケースがなかったためACできたみたいですがAfter Contestテストケースが追加されました)
そこで親ノードに至るまでの加算値を再帰関数の引数として残しておき、自身の加算値と合計したのちに子ノードに引き継ぐという方針を取りました
自分のノードが加算値の対象になるかを辞書型を用いて管理しています(リストでも問題なし)
pythonでの実装例
from collections import defaultdict from sys import setrecursionlimit # 再帰エラー対策 setrecursionlimit(10 ** 7) n,q = map(int,input().split()) connection = [[] for _ in range(n+1)] counter = [0 for _ in range(n+1)] visited = [0 for _ in range(n+1)] point_dic = defaultdict(int) for _ in range(n-1): a,b = map(int,input().split()) connection[a].append(b) connection[b].append(a) # 各ノードの加算値を記録 for _ in range(q): p,x = map(int,input().split()) point_dic[p] += x # 現在のノード(node)とそれまでに加算された値(before_point) def dfs(node,before_point): if visited[node]: return # 現在のノードの加算値を足す if point_dic[node]: point = point_dic[node]+before_point else: point = before_point # 訪問済みにする visited[node] = 1 counter[node] += point for i in connection[node]: if visited[i]: continue dfs(i,point) # 再帰して子ノードに引き継ぐ dfs(1,0) print(' '.join(map(str, counter[1:])))
27 JOI 2009 予選 4 - 薄氷渡り
問題文
atcoder.jp
氷を適切に割っていった際に何区間移動できるかを聞いた問題です
移動を行う際に、現在の深さを引数にとっておき、移動後の深さがそれまでの最大深さより大きい値になった場合更新をします
またその地点を訪問した際の広場のリスト(どこまで氷を割ったかのリスト)を見るために、再帰したときに一旦氷を割る動作を行い
その地点から行きうるすべての地点を探索し終わったら氷を元の状態に戻します
M*N個のすべての地点からスタートした際の最大深さがどうなるかを調べればOKです
現在の状態をこのように保持するやり方は知らなかったので新鮮でした
pythonでの実装例
m = int(input()) n = int(input()) place = [list(map(int,input().split())) for _ in range(n)] move = [[-1,0],[1,0],[0,-1],[0,1]] max_depth = 0 # 最大で行ける深さ def dfs(x,y,now_depth): global depth place[y][x] = 0 # 氷を割る if now_depth > depth: depth += 1 # 深さの最大値を超えた場合更新する for x_move,y_move in move: nx = x+x_move ny = y+y_move if 0 <= nx < m and 0 <= ny < n and place[ny][nx] == 1: dfs(nx,ny,now_depth+1) place[y][x] = 1 # 元に戻す(地点を探索する前のリストの状態にする) # 全てのマス目に対して可能性を試す for i in range(n): for j in range(m): if place[i][j] == 0: continue depth = 0 dfs(j,i,1) max_depth = max(max_depth,depth) print(max_depth)
Atcoder水色への精選 100 問 二分探索(Python)
水色になるための精選100問、前回から大分空いてしまいましたが引き続き更新していきます
今回は18~23までの「二分探索」です
前回↓
順列全探索
algo-archit.hatenablog.com
目次
- 18 ALDS_4_B - 二分探索
- 19 JOI 2009 本選 2 - ピザ
- 20 AtCoder Beginner Contest 077 C - Snuke Festival
- 21 AtCoder Beginner Contest 023 D - 射撃王
- 22 AtCoder Regular Contest 054 B - ムーアの法則
- 23 JOI 2008 本選 3 - ダーツ
18 ALDS_4_B - 二分探索
問題文
http://judge.u-aizu.ac.jp/onlinejudge/description.jsp?id=ALDS1_4_B
pythonの二分探索ライブラリであるbisectを使います
bisect.bisect_right(もしくはbisect.bisect)で挿入したい数字をリストに入れたときの右端のインデックスを
bisect.bisect_leftで挿入したい数字をリストに入れたときの左端のインデックスを返してくれます
今回の問題ではこの両者の差が1以上の場合にカウントすることで解くことができます
pythonでの実装例
import bisect n = int(input()) s = list(map(int,input().split())) q = int(input()) t = list(map(int,input().split())) point = 0 for i in t: if bisect.bisect_right(s,i)-bisect.bisect_left(s,i) > 0: point += 1 print(point)
19 JOI 2009 本選 2 - ピザ
問題文
https://www.ioi-jp.org/joi/2008/2009-ho-prob_and_sol/2009-ho.pdf#page=4
ジャッジページ
atcoder.jp
店の配置を記録するリストの先頭と末尾に0とdを追加してあげればOKです
右側と左側のそれぞれ最も近い位置の店のうち、距離が短い方を答えに足します
pythonでの実装例
import bisect d = int(input()) n = int(input()) m = int(input()) # 店の座標(1番目の店だけ先頭と後方につける) shop = [0]+[int(input()) for _ in range(n-1)] + [d] shop.sort() # 配送先 delivery = [int(input()) for _ in range(m)] ans = 0 for i in delivery: # 隣の一番近い店の座標を求める rightside_shop = shop[bisect.bisect(shop,i)] leftside_shop = shop[bisect.bisect(shop,i)-1] ans += min(abs(rightside_shop-i),abs(leftside_shop-i)) print(ans)
20 AtCoder Beginner Contest 077 C - Snuke Festival
問題文
atcoder.jp
各段ごとにパターンを列挙していては到底間に合いそうにありません
そこで少し工夫をして解く必要があります
祭壇のうち真ん中の段について残り二つの段については条件が一度に定まることに注目します
中段の祭壇について全探索を行い、条件を満たす上段と下段の組み合わせを二分探索によって求めることで計算を高速にすることができます
pythonでの実装例
import bisect n = int(input()) a = list(map(int,input().split())) b = list(map(int,input().split())) c = list(map(int,input().split())) a.sort() c.sort() ans = 0 C = len(c) # Bを中心に考える for i in b: ans += (bisect.bisect_right(a,i-1))*(C-bisect.bisect_left(c,i+1)) print(ans)
21 AtCoder Beginner Contest 023 D - 射撃王
問題文
atcoder.jp
すべての風船のペナルティをp以下にでき、かつ各風船を割るときの時間が負にならないようなpの値を二分探索で求めます
条件を満たさなかった場合pの下限を変更、満たす場合pの上限を変更することで最終的に答えを求めることができます
こういった形式の類題として以下の問題も参考になると思います
atcoder.jp
pythonでの実装例
import numpy as np n = int(input()) h,s = [],[] for _ in range(n): i,j = map(int,input().split()) h.append(i) s.append(j) h = np.array(h,dtype=np.int64) s = np.array(s,dtype=np.int64) # 全てのペナルティをp以下にできるかで二分探索 hi = 10**18 low = 0 def min_pena(p): # 各風船のペナルティをp以下にするためにかかる時間 time = (p-h)//s time.sort() # 各風船の時間がマイナスになっていないかを判定 return (time >= np.arange(n)).all() while hi-low > 1: mid = (hi+low)//2 if min_pena(mid): hi = mid else: low = mid print(hi)
22 AtCoder Regular Contest 054 B - ムーアの法則
問題文
atcoder.jp
様々な解法が考えられる問題ですが、今回は二分探索の解き方でやります
三分探索のpythonに関する解法はじゅっぴーさんのこちらのブログが参考になりました
↓
juppy.hatenablog.com
問題の式は凸関数と凸関数の和なので凸関数になります(解説文引用)
式として書くと
です
これを微分すると
になり、このが0を取るようなxの値を求め、に代入した答えを2分探索すればOKです
実際にはとなるxを計算すると
となるのでこれを代入しても正解を得られます
今回のように微分の式が複雑で解きにくい場合はsympyなどで正解となるxを求めてプログラムに代入するだけでも大丈夫だと思います(REが出るので直接使うことはできませんが)
あとhighの値を極端に大きく設定するとオーバーフローエラーを起こすので注意です
pythonでの実装例
from math import log p = float(input()) # かかる時間の計算式 def f(x): return x+p/2**(2*x/3) # f'(x) : 微分したもの def diff(x): return 1-2*p*log(2)/(3*2**(2*x/3)) high = 10**3 low = 0 # 傾きが0になるxを2分探索(x<0の場合もあるためlow=0に設定) while high-low > 10**(-10): mid = (high+low)/2 if diff(mid) < 0: low = mid else: high = mid print(f(high))
23 JOI 2008 本選 3 - ダーツ
問題文
atcoder.jp
※リンク先からさらに問題文のpdfを開く必要があります
ダーツを投げなかった場合を0点として考えます
4本のダーツの得点の組み合わせは0点の時も含めて(n+1)**4通りもあるのでn = 1000の時にTLEを起こしてしまいます
そこで最初に2本投げたときの得点の合計値の可能性を全列挙し、ソートしておきます
その後残りの2本を投げたときの得点の可能性を求めます
この際、「m点を超えないような残り2本の得点の合計(m-i番目の値)が取りうる最大値」を先ほどソートしたリストを使って二分探索します
合計得点がmを超えなかったもののうち、最大となる得点が答えです
pythonでの実装例
import bisect from itertools import combinations_with_replacement n,m = map(int,input().split()) p = [int(input()) for _ in range(n)]+[0] # 投げなかった時の0点も含める # ダーツ2本の合計得点の組み合わせ全列挙(同じダーツを選ぶ場合があることに注意) two_darts = [sum(i) for i in combinations_with_replacement(p,2)] two_darts.sort() max_point = 0 # m-i以下を満たす範囲での残り2本投げた際の最大得点を求める for i in two_darts: ans = two_darts[bisect.bisect_right(two_darts,m-i)-1]+i if ans > m: continue max_point = max(max_point,ans) print(max_point)
Atcoder水色への精選 100 問 全探索:順列全探索(Python)
水色になるための精選100問、
今回は15~17までの「全探索:順列全探索」です
前回↓
ビット全探索
algo-archit.hatenablog.com
目次
- 15 AtCoder Beginner Contest 145 C - Average Length
- 16 AtCoder Beginner Contest 150 C - Count Order
- 17 ALDS_13_A - 8 クイーン問題
15 AtCoder Beginner Contest 145 C - Average Length
問題文
atcoder.jp
座標群を並び替え、どの順番で行くかの順列を全探索すればOKです
距離の計算だけ注意です
pythonでの実装例
from itertools import permutations import math n = int(input()) li = [tuple(map(int,input().split())) for _ in range(n)] # 2点間の距離 def length(x1,y1,x2,y2): return(math.sqrt(abs(x2-x1)**2+abs(y2-y1)**2)) point = 0 for i in permutations(li): for j in range(1,n): point += length(i[j-1][0],i[j-1][1],i[j][0],i[j][1]) print(point/math.factorial(n))
16 AtCoder Beginner Contest 150 C - Count Order
問題文
atcoder.jp
並び替えられたリストから、何番目に出てくるかを調べる問題です
こういった時、pythonのitertools関数のpermutationを使うとリストの順列を簡単に生成できるので便利です
for i , j enumerate( list ) の形でリストのインデックスと中身を同時に取り出すことができます
(python辞書型のitemsのようなもの)
pythonでの実装例
from itertools import permutations n = int(input()) li = [i for i in range(1,n+1)] p = tuple(map(int,input().split())) q = tuple(map(int,input().split())) # pとqが何番目に出てくるかを記録 for i,j in enumerate(permutations(li)): if j == p: num_p = i if j == q: num_q = i print(abs(num_p-num_q))
17 ALDS_13_A - 8 クイーン問題
問題文
http://judge.u-aizu.ac.jp/onlinejudge/description.jsp?id=ALDS1_13_A
8×8マスのうちどの8マスにコマを置くかを全通り考えてしまうと
通りもあるので間に合いそうもありません
そこで正解となる置き方において、どの行でもクイーンの個数は1個しかないことに注目します
そのような行の並べ方は8個存在するので、それをどの順番で積み重ねていくかを全探索です
次に、生成された盤面が、先に与えられるK個のコマの配置と一致しているかを考えます
その後、縦方向と横方向にクイーンが被ることはないので、残り斜め方向の被りがないかだけチェックします
チェックにはいろいろ方法がありそうですが私は「右上(左上)からの距離が等しいマスにクイーンは2つ以上存在しない」という条件で探索しました
以下は4×4マスの例です
これで斜め方向にクイーンが2個以上存在するかを調べることができます
右下斜め方向、左下斜め方向が条件を満たすような盤面は1つに限られるのでそれを出力します
pythonでの実装例
from itertools import permutations n = int(input()) li = [list(map(int,input().split())) for _ in range(n)] chess = [["." for i in range(8)]for j in range(8)] for i in range(8): chess[i][i] = "Q" # 行のchessの並べ方を探索 for patern in permutations(chess): for y,x in li: if patern[y][x] != "Q": break else: rd = [0]*15 # i+jの合計を記録(右下斜め方向) ld = [0]*15 # 7-i+jの合計を記録(左下斜め方向) for i in range(8): for j in range(8): if patern[i][j] == "Q": rd[i+j] += 1 ld[i+(7-j)] += 1 for k in range(15): # 各斜め方向には1つまでしかQがあってはならない if rd[k] > 1 or ld[k] > 1: break else: for l in patern: print("".join(l))
↓次回 二分探索
algo-archit.hatenablog.com
Atcoder水色への精選 100 問 全探索:ビット全探索(Python)
水色になるための精選100問、
今回は10~14までの「全探索:ビット全探索」です
(※bit全探索を知らない方へ:bit全探索に関するpythonの詳しい解説は以下がおすすめです)
qiita.com
前回↓
工夫して通り数を減らす全列挙
algo-archit.hatenablog.com
目次
- 10 ALDS_5_A - 総当たり
- 11 AtCoder Beginner Contest 128 C - Switches
- 12 AtCoder Beginner Contest 002 D - 派閥
- 13 JOI 2008 予選 5 - おせんべい
- 14 Square869120Contest #4 B - Buildings are Colorful!
10 ALDS_5_A - 総当たり
問題文
http://judge.u-aizu.ac.jp/onlinejudge/description.jsp?id=ALDS1_5_A&lang=ja
N個の数字からどれを選ぶのかをbit全探索します
ここで各bitごとに0か1かの2通りの可能性があることから
通りの可能性を試せば大丈夫です
今回はmax_n = 20なので100万通り
1 << n と は同じ意味なことに注意です
pythonでの実装例
n = int(input()) a = list(map(int,input().split())) pos = set() # n桁分のbit探索 for i in range(1 << n): point = 0 for j in range(n): # j個目の商品が含まれるか if (i >> j)&1: point += a[j] # 作ることができるものをsetに加えていく pos.add(point) q = int(input()) m = list(map(int,input().split())) for i in m: if i in pos: print("yes") else: print("no")
11 AtCoder Beginner Contest 128 C - Switches
問題文
atcoder.jp
問題文は少し難解ですが、N個のスイッチのうちONにするものをbit全探索していき、各パターンで電球は点灯するのかどうかを検証します
pythonでの実装例
n,m = map(int,input().split()) li = [list(map(int,input().split()))[1:] for _ in range(m)] p = list(map(int,input().split())) ans = 0 for i in range(1 << n): switch = set() # どのスイッチがONになっているか for j in range(n): if (i >> j)&1: switch.add(j+1) # 点灯条件を満たすか for k in range(m): point = 0 for l in li[k]: if l in switch: point += 1 if point%2 != p[k]: break else: ans += 1 print(ans)
12 AtCoder Beginner Contest 002 D - 派閥
問題文
atcoder.jp
派閥に含める人の選び方をbit全探索します
選ばれた人から2人を選ぶときに矛盾が生じないかを確認すれば大丈夫です
pythonでの実装例
from itertools import combinations n,m = map(int,input().split()) rel = {} # ハッシュテーブルで管理 for _ in range(m): x,y = map(int,input().split()) rel[x,y] = True max_num = 1 # 作成可能な派閥の最大人数 # どの人を派閥に加えるか for i in range(1 << n): human = [] for j in range(n): if (i >> j)&1: human.append(j+1) for k,l in combinations(human,2): if (k,l) not in rel: break else: max_num = max(max_num,len(human)) print(max_num)
13 JOI 2008 予選 5 - おせんべい
問題文
atcoder.jp
どの列のおせんべいを裏返すかをbit全探索します
裏返すときにはその列の要素全体に対して1とXORでかけ合わせます
( 0 XOR 1 = 1, 1 XOR 1 = 0 なので )
こういった行列全体への計算にはnumpyを使うと便利です
また裏返し処理が終わったあと、各列の裏返っているせんべいの枚数をカウントします(Aと仮定)
この際に、その列の裏返っている枚数がR-A枚未満だった場合は列をひっくり返すのが最適です
pythonでの実装例
import numpy as np r,c = map(int,input().split()) senbei = [list(map(int,input().split())) for _ in range(r)] senbei = np.array(senbei,dtype=np.int64) senbei_max = 0 # せんべいの最大枚数 # どの列を裏返すかをbit全探索する for i in range(1 << r): senbei_c = np.copy(senbei) for j in range(r): if (i >> j)&1: senbei_c[j] ^= 1 # ひっくり返す count = senbei_c.sum(axis=0) # 現在の状態の列の裏返っている個数 senbei_num = np.maximum(count,r-count).sum() senbei_max = max(senbei_num,senbei_max) print(senbei_max)
14 Square869120Contest #4 B - Buildings are Colorful!
問題文
atcoder.jp
どの建物をk個以上見えるようにしたいかをbit探索します
i番目の建物が見えてほしいとき、i-1番目までの最高高さがi番目の建物以上の場合i番目の建物の高さをmax(a[ : i ])+1にします
pythonでの実装例
n,k = map(int,input().split()) a = list(map(int,input().split())) ans = 10**20 # どのビルを見るかをbit全探索 for i in range(1 << n): li = [] for j in range(n): if (i >> j)&1: li.append(j) if len(li) < k: continue point = 0 now = a[0]# 最大高さ for l in range(1,n): if l in li and a[l] <= now: point += now+1-a[l] now += 1 # 最大高さ更新 now = max(now,a[l]) ans = min(ans,point) print(ans)
次回↓
順列全探索(15~17まで)
algo-archit.hatenablog.com
Atcoder水色への精選 100 問 全探索 : 工夫して通り数を減らす全列挙(Python)
引き続き水色になるための精選100問を解いていきます
今回は5~9までの「全探索 : 工夫して通り数を減らす全列挙」です
前回↓
全列挙
algo-archit.hatenablog.com
目次
- 5 AtCoder Beginner Contest 095 C - Half and Half
- 6 三井住友信託銀行プログラミングコンテスト 2019 D - Lucky PIN
- 7 JOI 2007 本選 3 - 最古の遺跡
- 8 Square869120Contest #6 B - AtCoder Markets
- 9 JOI 2008 予選 4 - 星座探し
5 AtCoder Beginner Contest 095 C - Half and Half
問題文
atcoder.jp
ABピザを2枚で1セットC×2円で考えると
ABピザを何セット分買うかを全探索することで残りのA,Bピザを何枚買うかを求めることができます
pythonでの実装例
a,b,c,x,y = map(int,input().split()) ans = 10**10 # ABピザ2枚セットを何枚分買うか for i in range(max(x,y)+1): point = 2*c*i # 足りない分は買い増し if i < x: point += a*(x-i) if i < y: point += b*(y-i) ans = min(ans,point) print(ans)
6 三井住友信託銀行プログラミングコンテスト 2019 D - Lucky PIN
問題文
atcoder.jp
暗証番号の個数はせいぜい000~999までの1000通りなので
これらの番号が文字列から生成することができるかを全列挙することで問題は解けます
しかしながら以下のようなコードをPythonで提出しようとしても間に合いません(PyPy3だと500msくらいで間に合う)
TLE例
n = int(input()) s = input() point = 0 # 暗証番号を全探索する for i in range(1000): t = str(i).zfill(3) now = 0 # sで暗証番号が作れるかを検証 for j in s: if j == t[now]: now += 1 if now == 3: point += 1 break print(point)
そこで少し工夫をして、暗証番号を1桁づつ見ていき、Sから作ることができない数を除外してやると間に合わせることができます
pythonでの実装例
n = int(input()) s = input() point = 0 # 100の位 for i in range(10): # Sのn-2番目までにiがあるか str_i = str(i) for a in range(n-2): if s[a] == str_i: now_i = a break # 作れなかった場合打ち切り else: continue # 10の位 for j in range(10): str_j = str(j) for b in range(now_i+1,n-1): if s[b] == str_j: now_j = b break else: continue # 1の位 for k in range(10): str_k = str(k) for c in range(now_j+1,n): # 暗証番号がそろった if s[c] == str_k: point += 1 break print(point)
7 JOI 2007 本選 3 - 最古の遺跡
問題文
https://www.ioi-jp.org/joi/2006/2007-ho-prob_and_sol/2007-ho.pdf#page=5
柱を2点選び、残りの2点を正方形の形になるように座標を求めた際に、その位置に柱が存在するかを確認できればOKです
注意書きがあった通りPythonで解こうとすると下手なコードではTLEを起こしてしまいます
itertoolsのcombinationやset型を用いることでなんとか間に合わせることができました
pythonでの実装例
from itertools import combinations n = int(input()) xy = [tuple(map(int,input().split())) for _ in range(n)] set_xy = set(xy) # 面積の最大値 max_s = 0 # 2点の組み合わせ for (x1,y1),(x2,y2) in combinations(xy,2): dx = x1-x2 dy = y1-y2 # 残りの2点の座標が柱として存在するかを検索 if (x2-dy,y2+dx) in set_xy and (x1-dy,y1+dx) in set_xy: # 存在した場合面積を更新する max_s = max(max_s,(x1-x2)**2+(y1-y2)**2) print(max_s)
8 Square869120Contest #6 B - AtCoder Markets
問題文
atcoder.jp
与えられた地点の最小値から最大値までの範囲でスタートとゴールを全探索します
A A地点 -> B地点 -> ゴール
となるように移動するのが最短経路になります
中央値を使った求め方もできるようです
以下の解答リンク参照
https://img.atcoder.jp/s8pc-6/editorial.pdf
pythonでの実装例
n = int(input()) ab = [tuple(map(int,input().split())) for _ in range(n)] set_ab = set() ans = 10**20 for i,j in ab: set_ab.add(i) set_ab.add(j) set_ab = list(set_ab) S = len(set_ab) # スタートとゴールの範囲を全探索する for i in range(S-1): for j in range(i,S): point = 0 for a,b in ab: point += abs(set_ab[i]-a) # start -> a point += abs(set_ab[j]-b) # b -> goal point += b-a # a -> b ans = min(ans,point) print(ans)
9 JOI 2008 予選 4 - 星座探し
問題文
atcoder.jp
与えられたN個の星を星座の中の1地点と一致させるときのずれを記録し、残りのN-1個の星座+ずれ分の座標がM個の星の中にあるかを全探索します
M個の座標をハッシュテーブル(Pythonの辞書型)で管理することで計算量O(N*M)で解くことができます(この問題の制限時間ではあまり関係ありませんが)
pythonでの実装例
m = int(input()) star = [tuple(map(int,input().split())) for _ in range(m)] star.sort() n = int(input()) stars = [tuple(map(int,input().split())) for _ in range(n)] stars.sort() # ハッシュテーブルで管理 dic = {} for i,j in stars: dic[i,j] = True for i,j in stars: # 星座の一つめの星とのずれを記録 x_gap = i-star[0][0] y_gap = j-star[0][1] for k,l in star: if (k+x_gap,l+y_gap) not in dic: break # breakが発生しなかった場合 else: print(x_gap,y_gap) exit()
次回↓
bit全探索
algo-archit.hatenablog.com
Atcoder水色への精選 100 問 全探索 : 全列挙(Python)
E869120さんが作成した、精選100問を解いていきます
今回は1~4までの「全探索 : 全列挙」をやります
qiita.com
目次
1 How many ways?
問題文
http://judge.u-aizu.ac.jp/onlinejudge/description.jsp?id=ITP1_7_B&lang=ja
2つ目までの範囲を決めたときの残った3番目の要素が目的の数値に等しいかをカウントします
(ちなみに3番目までを全探索したときでも程度なので間に合うっぽい)
pythonでの実装例
li = [] while True: n,m = map(int,input().split()) if n == 0 and m == 0: break li.append((n,m)) for n,m in li: point = 0 for one in range(1,n-1): for two in range(one+1,n): three = m-one-two if two < three < n+1: point += 1 print(point)
2 B - 105
問題文
atcoder.jp
1からNまでの奇数のうち、各数字の約数を全列挙したときの長さが8のものの個数をカウントします
pythonでの実装例
n = int(input()) # 約数列挙 def make_divisors(n): divisors = [] for i in range(1, int(n**0.5)+1): if n % i == 0: divisors.append(i) if i != n // i: divisors.append(n//i) divisors.sort() return divisors point = 0 # 奇数全探索 for i in range(1,n+1,2): j = make_divisors(i) if len(j) == 8: point += 1 print(point)
3 AtCoder Beginner Contest 122 B - ATCoder
問題文
atcoder.jp
部分文字列を全探索していきます(空文字は除く)
部分文字列の先頭と終了地点を決め、作られた文字列がACGT文字列だった場合をカウントします
ACGT文字列だった場合、さらに文字列の長さが今までのACGT文字列よりも長かった場合、更新をします
フラグ管理やmax関数を使うと便利です
pythonでの実装例
s = input() n = len(s) # 文字の長さ max_length = 0 # 最大長さ for i in range(n): for j in range(i,n): t = s[i:j+1] flag = True # ACGT文字列であるかのフラグ for k in t: if k not in "AGCT": flag = False if flag: max_length = max(max_length,len(t)) print(max_length)
4 C - カラオケ
問題文
atcoder.jp
1曲目と2曲目の組み合わせを求めておき、各パターンの得点を数えます
得点が過去の最大値を上回った場合は更新をします
pythonでの実装例
n,m = map(int,input().split()) a = [list(map(int,input().split())) for _ in range(n)] ans = 0 # 1曲目と2曲目を全探索する for i in range(m-1): for j in range(i+1,m): point = 0 # n人が曲を歌った時の得点を求める for k in range(n): point += max(a[k][i],a[k][j]) # 最大値を更新 ans = max(ans,point) print(ans)
次回↓
工夫して通り数を減らす全列挙
algo-archit.hatenablog.com