JOI2016予選E ゾンビ島

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

ダイクストラ法
幅優先探索

はじめに

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

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

問題

https://atcoder.jp/contests/joi2016yo/tasks/joi2016yo_e

この問題は幅優先探索とダイクストラ法を用いて解くことができます。やや発展的な内容ですので、ダイクストラ法の練習のためにはまず下記の問題に取り組むことをオススメします。

JOI2008予選F 船旅

GRL1A Single Source Shortest Path

  • さらに簡単です。AOJ(Aizu Online Judge)というサイトの問題です。ダイクストラ法の基礎を学びたければこちらも良いでしょう。

方針

基本的には、解説の通りに実装すれば良いです。
この問題の解法は 2 つの段階からなる.1 つ目は危険な町を列挙する段階,2 つ目は宿泊費の合計が最小になる経路を見つける段階である.

  1. 危険な町を列挙
    • 幅優先探索
  2. 宿泊費の合計が最小になる経路を探索
    • ダイクストラ法

コード

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
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
from collections import deque
import heapq
INF = 10**20

N,M,K,S = map(int,input().split())
P,Q = map(int,input().split())

zombie = []
for k in range(K):
zombie.append(int(input())-1)

link = [ [] for _ in range(N)]

for m in range(M):
a, b = map(int, input().split())
link[a-1].append(b-1)
link[b-1].append(a-1)

dist = [INF] * N
q = deque(zombie)
for v in zombie:
dist[v] = 0
while q:
node = q.popleft()
for next in link[node]:
if dist[next] != INF: # 探索済みの場合はパスされる。、
continue # distは小さい順にqに格納されるので、ゾンビからの最小距離を求めることがd系る
dist[next] = dist[node] + 1
q.append(next)

cost = [P] * N
for n in range(N):
if dist[n] <= S:
cost[n] = Q
if n in zombie:
cost[n] = INF
if n==0 or n==N-1:
cost[n] = 0

def dijkstra(s, g):
dists = [INF for i in range(N)]
dists[s] = 0
pq = [(0, s)] # 優先度付きキューのオブジェクトそのものはただのリスト
while(pq[0][1]!=g):
d, node = heapq.heappop(pq)
if (d > dists[node]): # 探索の対象にする必要があるか確認
continue
for next in link[node]:
c = cost[next]
if d + c < dists[next]: # 探索の対象にする必要があるか確認
dists[next] = d + c
heapq.heappush(pq, (dists[next],next))
return pq[0][0]
print (dijkstra(0,N-1))

記事情報

  • 投稿日:2020年4月26日
  • 最終更新日:2020年5月8日

GRL1A 単一始点最短経路

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

ダイクストラ法

はじめに

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

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

問題

http://judge.u-aizu.ac.jp/onlinejudge/description.jsp?id=GRL_1_A

この問題はダイクストラ法を用いて解くことができます。

ポイント

ダイクストラ法の計算量は下記のようになります。

1
2
3
- リスト: O(|V|^2)
- 優先度付きキュー(二分ヒープ): O((|V|+|E|)log|V|)
- 優先度付きキュー(フィボナッチヒープ): O(|E|+|V|log|V|)

いつも優先度付きキューを使えば良いというわけではありません。完全グラフの場合は|V|^2と|E|のオーダーが等しくなるため、リストが最も高速になります。

しかし、この問題の場合は優先度付きキューを用いるのが良いでしょう。Pythonで優先度付きキュー(二分ヒープ)を実装する際は、標準ライブラリのheapqを利用すると良いです。

コード

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
import heapq
INF = 10**10

V, E, r = map(int,input().split())
adj = [[] for i in range(V)] # 隣接リスト

for e in range(E):
s, t, d = map(int,input().split())
adj[s].append((t,d))

dists = [INF for i in range(V)] # 重みの和
dists[r] = 0
pq = [(0, r)] # 二分ヒープの実体はリストやタプルとして表現する
  # (重みの和, ノード番号)
while(pq):
d, node = heapq.heappop(pq)
if (d > dists[node]): # 探索の対象にする必要があるか確認
continue
for next, cost in adj[node]:
if d + cost < dists[next]:# 探索の対象にする必要があるか確認
dists[next] = d + cost # 次のノードにおける重みの和
heapq.heappush(pq, (dists[next],next))
for d in dists:
if d == INF:
print ('INF')
else:
print (d)

記事情報

  • 投稿日:2020年4月25日
  • 最終更新日:2020年5月10日

JOI2014予選E タクシー

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

ダイクストラ法
幅優先探索

はじめに

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

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

問題

https://atcoder.jp/contests/joi2014yo/tasks/joi2014yo_e

この問題は幅優先探索とダイクストラ法を用いて解くことができます。

注意

この問題をPythonで解こうとした場合、多くの実装ではPython3で解くとTLEPyPy3で解くとMLEになってしまいます。それを回避するためには本質的ではない計算量の削減も必要です。他の言語で挑戦するか、どうしてもPythonで挑戦したい方はACされている方のコードを参考にしましょう。(かなり苦しみました)

ダイクストラ法の練習のためには、こちらの問題よりも下記の問題に取り組むことをオススメします。

JOI2016予選E ゾンビ島

  • ほぼ同様の問題です。こちらはPython3で問題なく通せます。

JOI2008予選F 船旅

  • 上の問題よりも簡単です。

GRL1A Single Source Shortest Path

  • さらに簡単です。AOJ(Aizu Online Judge)というサイトの問題です。ダイクストラ法の基礎を学びたければこちらも良いでしょう。

方針

基本的には、解説の通りに実装すれば良いです。

  • 町iから町jまでタクシーを途中で乗り換えることなく行けるかどうかを判定する
    • 幅優先探索
  • 町iから町jまでタクシーを途中で乗り換えることなく行ける場合,町iから町jにコストCiの辺があると考える
  • 単一始点最短経路を求める
    • ダイクストラ法

コード

こちらのコードを参考にしました

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 heapq
INF = 10**10

def dijkstra(s, g):
dists = [INF for i in range(N)]
dists[s] = 0
pq = [(0, s)] # 優先度付きキューのオブジェクトそのものはただのリスト
while(pq):
d, node = heapq.heappop(pq)
if (d > dists[node]): # 探索の対象にする必要があるか確認
continue
for next, cost in adj[node]:
if d + cost < dists[next]:
dists[next] = d + cost
heapq.heappush(pq, (dists[next],next))
if dists[g] == INF:
return -1
else:
return dists[g]

N, K = map(int,input().split())
adj = [[] for i in range(N)]
ans = []
for k in range(K):
ls = list(map(int,input().split()))
if ls[0]==0:
ans.append(dijkstra(ls[1]-1, ls[2]-1))
else:
adj[ls[1]-1].append((ls[2]-1,ls[3]))
adj[ls[2]-1].append((ls[1]-1,ls[3]))
for a in ans:
print (a)

記事情報

  • 投稿日:2020年4月25日
  • 最終更新日:2020年5月8日

JOI2008予選F 船旅

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

ダイクストラ法

はじめに

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

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

問題

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

この問題はダイクストラ法を用いて解くことができます。

ポイント

ダイクストラ法は優先度付きキューを用いることで高速化できます。

Pythonで優先度付きキューを実装する際は、標準ライブラリのheapqを利用すると良いでしょう。

コード

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 heapq
INF = 10**10

def dijkstra(s, g):
dists = [INF for i in range(N)]
dists[s] = 0
pq = [(0, s)] # 優先度付きキューのオブジェクトそのものはただのリスト
while(pq):
d, node = heapq.heappop(pq)
if (d > dists[node]): # 探索の対象にする必要があるか確認
continue
for next, cost in adj[node]:
if d + cost < dists[next]: # 更新すべきか確認
dists[next] = d + cost
heapq.heappush(pq, (dists[next],next))
if dists[g] == INF:
return -1
else:
return dists[g]

N, K = map(int,input().split())
adj = [[] for i in range(N)]
ans = []
for k in range(K):
ls = list(map(int,input().split()))
if ls[0]==0:
ans.append(dijkstra(ls[1]-1, ls[2]-1))
else:
adj[ls[1]-1].append((ls[2]-1,ls[3]))
adj[ls[2]-1].append((ls[1]-1,ls[3]))
for a in ans:
print (a)

記事情報

  • 投稿日:2020年4月23日
  • 最終更新日:2020年5月8日

ABC074D Restoring Road Network

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

ワーシャルフロイド法

はじめに

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

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

問題

https://atcoder.jp/contests/abc074/tasks/arc083_b

この問題を解く前に、ワーシャルフロイド法を用いた最短経路問題の解き方について理解しておくことをお勧めします。

解説

2つの問題を同時に解いていきます

  1. 都市uから都市vへの最短経路の長さとなるような道路の構造が存在するか
  2. 存在する道路の長さの和が最小となるようなものについて、その和

都市uから都市vへの最短経路の長さとなるような道路の構造が存在するか

都市uから都市vへ移動する間にどの別の1つの都市を通っても、表に記された値より小さいコストで移動できないことを確認すれば良いです。

存在する道路の長さの和が最小となるようなものについて、その和

2つ目の問題は、都市uから都市vへ移動する間にどの別の1つの都市を通っても、表に記された値と等しいコストで移動できない場合、直接結ぶ道路が必要ということになります。そのようなコストを足し合わせていけば良いです。

ポイント

  • u < vとする
    • 道路は双方向に結ばれているので、二重にカウントしてはならない
    • u != vとした方が、条件分岐の実装が楽になる
  • cost[u][u] = INFとする
    • cost[u][u]=0としてしまうと、u->u->vu->vの値が常に等しくなる。これを避けた方が条件分岐の実装が楽になる

コード

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())
INF = 10**10
cost = [[INF]*N for _ in range(N)] # INFで初期化v

for u in range(N):
C = list(map(int, input().split()))
for v in range(N):
if u==v: # 自身への道路はINFのままにする
continue
cost[u][v] = C[v]

ans = 0
for u in range(N):
for v in range(u+1,N): # u < v
mn = INF # u-w-vのような経路の中で最小の距離を求める
for w in range(N):
mn = min(mn, cost[u][w]+cost[w][v])
if (cost[u][v] > mn): # 与えられた行列が最短経路ではない
print (-1) # 終了して良い
exit()
elif (cost[u][v] < mn): # 等しくない場合はu-vを直接結ぶ道路が必要
ans += cost[u][v]
print (ans)

記事情報

  • 投稿日:2020年4月22日
  • 最終更新日:2020年5月8日

ABC079D Wall

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

ワーシャルフロイド法

はじめに

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

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

問題

https://atcoder.jp/contests/abc079/tasks/abc079_d

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

解説

ある数字を1に変えるために他の数字を経由することで、直接書き換えるよりコストを節約できるケースがあります。

つまり、この問題は魔力をコストとした最短経路問題と言えます。

一旦、最短経路を求めてしまえば、あとは壁に書かれている数字に対し1への変換コストを求めて足していけば良いです。

解法としては

  • ダイクストラ法をN回適用する
  • ワーシャルフロイド法
    などが考えられます。どちらも計算量はO(N^3)です。

今回の問題は隣接行列が対称行列となっていない点に注意が必要ですが、これらのアルゴリズムは問題なく適用できます。

コード

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
H, W = map(int, input().split())
INF = 10**10
N = 10
cost = [[INF]*N for _ in range(N)] # 隣接行列

for i in range(N):
C = list(map(int, input().split()))
for j in range(N):
cost[i][j] = C[j]

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])

ans = 0 # 和
for h in range(H):
A = list(map(int, input().split()))
for a in A:
if a!=-1: # -1出なければ足す
ans += cost[a][1] # 対称行列ではないので注意する

print (ans)

記事情報

  • 投稿日:2020年4月21日
  • 最終更新日:2020年5月8日

ABC012D バスと避けられない運命

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

ワーシャルフロイド法

はじめに

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

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

問題

https://atcoder.jp/contests/abc012/tasks/abc012_4

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

解説

この問題は自宅から会社まで出来るだけ時間がかからないようにしたいので、時間をコストとした最短経路問題と言えます。

さらに、自宅と会社の位置は決まっていないため全てのバス停間の最短経路を求める必要があります。このような問題は最短経路問題の中でも全点対最短経路問題と言います。

解法としては

  • ダイクストラ法をN回適用する
  • ワーシャルフロイド法
    などが考えられます。どちらも計算量はO(N^3)です。

今回は実装が簡単である解法、ワーシャルフロイド法で解きたいと思います。Python3だとTLEとなってしまうので、PyPy3を利用します。

コード

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
N, M = map(int, input().split())
INF = 10**10
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-1][b-1] = t
cost[b-1][a-1] = 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])

ans = INF
for n in range(N):
mx = max(cost[n])
if mx < ans:
ans = mx
print (ans)

記事情報

  • 投稿日:2020年4月20日
  • 最終更新日:2020年5月8日

ABC163

はじめに

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

前回のコンテスト(ABC162)から、使用可能言語が変更となりました。
Pythonのバージョンは3.8.2に変更されました。

これでようやく、gcd(最大公約数)のインポートのストレス(Python競プロでやりがちなミス)から解放されました。

前回の解説はこちら

A - Circle Pond

https://atcoder.jp/contests/abc163/tasks/abc163_a

特に解説はいらないと思います。

1
2 * 半径 * 円周率

を計算すれば良いです。円周率は標準ライブラリmathを使うと良いでしょう。

1
2
3
import math
r = int(input())
print (2*math.pi*r)

B - Homework

https://atcoder.jp/contests/abc163/tasks/abc163_b

この問題も簡単ですね。リストAsumを考えれば良いだけです。

1
2
3
4
5
6
7
N, M = map(int,input().split())
A = list(map(int,input().split()))
ans = N-sum(A)
if ans < 0:
print (-1)
else:
print (ans)

C - management

https://atcoder.jp/contests/abc163/tasks/abc163_c

リストの特定の要素へのアクセスはO(1)ですから、愚直に実装すれば問題なく間に合います。
v
社員番号は1から始まるので、0-オリジンの言語の場合はインデックスのずれに気をつけましょう。

1
2
3
4
5
6
7
N = int(input())
A = list( map(int,input().split()))
cnt = [0] * N
for a in A:
cnt[a-1]+=1
for n in range(N):
print (cnt[n])

D - Sum of Large Numbers

https://atcoder.jp/contests/abc163/tasks/abc163_d

iの値が異なるパターン同士は和が必ず異なる

N+1個の数のうちi個を選び和を計算する状況を想定しましょう。この時10^100が十分に大きいためiの値が異なるパターン同士は和が必ず異なります。

10^100を無視

そしてこの点だけ考慮できれば、10^100の部分は全く無視してよく、10^100+aaと読み替えられます。

部分問題に分解

iの値が異なるパターン同士は和が必ず異なるのでiは固定して部分問題を解き、iKからN+1までループを回せば良いです。

さて、iを固定した時の部分問題について考えます。

具体例を考える

1
2
N = 9
i = 3

N=9なので、選択可能な数は以下のようになります。

1
{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}

i=3なので、この中から

1
{1, 4, 7}

のように数字を3つ選び組み合わせを考えます。

さて、全ての組み合わせを考えた際に和は何通りあるでしょうか。

全ての和は列挙すると以下のようになります。

1
[3, 4, 5,..., 23, 24]

実は、作ることができる和は連続する値になっています。これは

1
1+4+7 = 12

となっているときに

1
2
3
2+4+7 = 13
1+5+7 = 13
1+4+8 = 13

のように、いずれかの数を1増やすことで和を1増やせるためです。

これができない状況は、和が最大となる時

1
{7, 8, 9}

であることが分かります。

この作ることができる和は連続する値になっているという性質を使うと、組み合わせの数は

1
和の最大値 - 和の最小値 + 1

で求められると分かります。

和の最大値

1
7 + 8 + 9

和の最小値

1
0 + 1 + 2

なので、

和の最大値 - 和の最小値

1
2
3
4
  (7 + 8 + 9)
- (0 + 1 + 2)
--------------
7 + 7 + 7

となります。一般的には
i * (N+1-i)となります。

この計算はO(1)なので、
問題全体としては(iKからN+1までループ回すので)、O(N)で解くことができます。

なお、さらにシグマ計算をすることでループを回す必要すらなくなりO(1)で解けます。

1
2
3
4
5
6
7
8
N, K = map(int,input().split())
ans = 0
MOD = 10**9 + 7
for i in range(K,N+2):
x = (N+1-i) * i + 1
ans += x
ans %= MOD
print (ans)

記事情報

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

ABC134E Sequence Decomposing

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

動的計画法

はじめに

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

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

問題

https://atcoder.jp/contests/abc134/tasks/abc134_e

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

この問題を解く前に

この問題はやや発展的な内容です。動的計画法や二分探索に不安がある方は、まずABC006D トランプ挿入ソートを先に解いても良いでしょう。

方針

  • 解説pdfの通り数列の後ろから順に考える
  • 最長増加部分列問題(LIS)に帰着できます
    • 最長増加部分列問題(LIS)は動的計画法と二分探索により実装できます。

解説

数列の後ろから順に適切な割り当てを考えていきます。そして、この考え方が最長増加部分列問題(LIS)に帰着できることを確認しましょう。

具体的に、入力例1について考えてみよう。

1
2
3
4
5
6
5 # N
2
1
4
5
3
  1. 初期化
    以下のような表を更新していきます。(動的計画法)

    色の種類 1 2 3 4
    各色ごとの最小値 INF INF INF INF
  2. 3に色を割り当てる

    最初なので、新規に色を作成する(色1

    色の種類 1 2 3 4
    各色ごとの最小値 3 INF INF INF
  3. 5に色を割り当てる

    3より大きいので、新規に色を作成する他ない(色2

    色の種類 1 2 3 4
    各色ごとの最小値 3 5 INF INF
  4. 4に色を割り当てる
    二つの選択肢がある。

    • 新規に色を作成する(色3

    • 色2の最小値を更新する

      しかし、最小値の更新が可能な場合は明らかに更新を行うべきである。

      色の種類 1 2 3 4
      各色ごとの最小値 3 4 INF INF
  5. 1に色を割り当てる
    更新可能な箇所が複数ある。今後の可能性を広くするために、より小さな値を更新するべきである。
    すなわち1 < 3 < 4であるので、31に更新する

    色の種類 1 2 3 4
    各色ごとの最小値 1 4 INF INF
  6. 2に色を割り当てる

    色の種類 1 2 3 4
    各色ごとの最小値 1 2 INF INF

さて、これまでの操作から色の種類を最小化できたことがわかる。(INFとなっていない色の種類数、すなわち2となる)

各色ごとの最小値は単調増加なので二分探索によって、INFとなっていない色の種類数を求めることができる。

ここまでの処理は、最長増加部分列問題(LIS)と等価である。

コード

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import bisect
import sys
input = sys.stdin.readline
N = int(input())
A = []
for n in range(N):
A.append(int(input()))
A = A[::-1] # 数列の後ろから処理
INF = int(1e+9 + 10)
lis = [INF] * N # 初期化
for n in range(N):
i = bisect.bisect(lis, A[n]) # 更新すべき最小値
lis[i] = A[n]
print (bisect.bisect_left(lis, INF)) # `INF`となっていない色の種類数

記事情報

  • 投稿日:2020年4月19日
  • 最終更新日:2020年5月8日

ABC006D トランプ挿入ソート

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

動的計画法

はじめに

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

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

問題

https://atcoder.jp/contests/abc006/tasks/abc006_4

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

方針

  • 最長増加部分列(LIS)を求める
    • 動的計画法により実装する
  • N - 最長増加部分列の長さが解になる。

最長増加部分列(LIS)とは

  • ある数列から順番を変えずに任意の要素を取り出し新たな数列を作ることができます。
    これを部分列と呼びます。
  • 単調増加な数列を増加列と呼びます。

すなわち、ある数列の最長増加部分列とは、その数列の増加部分列のうち最も長いものの長さを表します。

ポイント

動的計画法により求める

部分増加列の長さ 0 1 2 N
列の右端の最小値 0 INF INF INF

具体的に、入力例1について考えてみよう。以下のようにカードが並んでいる。

1
2
3
4
5
6
7
6 # 枚数
1
3
5
2
4
6
  1. 初期化

    部分増加列の長さ 0 1 2 3 4 5 6
    列の右端の最小値 0 INF INF INF INF INF INF
  2. 1を挿入すべき場所を考える

    部分増加列の長さ 0 1 2 3 4 5 6
    列の右端の最小値 0 1 INF INF INF INF INF
  3. 3を挿入すべき場所を考える

    部分増加列の長さ 0 1 2 3 4 5 6
    列の右端の最小値 0 1 3 INF INF INF INF
  4. 5を挿入すべき場所を考える

    部分増加列の長さ 0 1 2 3 4 5 6
    列の右端の最小値 0 1 3 5 INF INF INF
  5. 2を挿入すべき場所を考える

    部分増加列の長さ 0 1 2 3 4 5 6
    列の右端の最小値 0 1 2 5 INF INF INF
  6. 4を挿入すべき場所を考える

    部分増加列の長さ 0 1 2 3 4 5 6
    列の右端の最小値 0 1 2 4 INF INF INF
  7. 6を挿入すべき場所を考える

    部分増加列の長さ 0 1 2 3 4 5 6
    列の右端の最小値 0 1 2 4 6 INF INF

列の右端の最小値の(INFを除いた)最大値は6であり、その時の部分列の長さは4である。これをカードの枚数Nから引いて、解は2となる

コード(50点)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
N = int(input())
cards = []
for n in range(N):
cards.append(int(input()))
INF = 10010
lis = [INF] * (N+1) # 列の右端の最小値
lis[0] = 0
ans = 0
for n in range(N):
for m in range(n+1): # 無駄にINFの領域を探索する必要はない
if lis[m] < cards[n] and cards[n] < lis[m+1]: # 今回は同じ値のカードはない
lis[m+1] = cards[n]
ans = max(ans, m+1)
break # 更新できたらループを抜けて良い
print(N-ans)

満点解法

上記のアルゴリズムは計算量がO(n^2)であるが、さらに高速化ができる。

列の右端の最小値が増加列であるということを利用して、二分探索を行えば良い。

コード(100点)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import bisect
N = int(input())
cards = []
for n in range(N):
cards.append(int(input()))
INF = 100010
lis = [INF]* (N+1)
lis[0] = 0
ans = 0
for n in range(N):
i = bisect.bisect(lis, cards[n]) # 二分探索
lis[i] = cards[n]
ans = max(ans, i)
print(N-ans)

記事情報

  • 投稿日:2020年4月18日
  • 最終更新日:2020年5月8日