ABC165

はじめに

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

A - We Love Golf

https://atcoder.jp/contests/abc165/tasks/abc165_a

シミュレーションをすれば十分に間に合います。

1
2
3
4
5
6
7
k = int(input())
a, b = map(int,input().split())
for i in range(a,b+1):
if i%k==0:
print ('OK')
exit()
print ('NG')

B - 1%

https://atcoder.jp/contests/abc165/tasks/abc165_b

入力例2Xの上限である10**18に対する答えが3760とわかります。

預金額は単調増加するので、3760回以上ループを回せば十分です。

1
2
3
4
5
6
7
X = int(input())
d = 100
for i in range(4000):
d = int(d * 1.01)
if d >= X:
print (i+1)
exit()

C - Many Requirements

https://atcoder.jp/contests/abc165/tasks/abc165_c

リストから要素を重複を許して選択し、新たなリストを作成します。ただし、新たなリストの各要素に対応する元リストのインデックス番号が単調増加するという条件を付けます。

これは深さ優先探索で解くことができますが、Pythonの場合はitertools.combinations_with_replacement
で直接求めることができます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import itertools
N, M, Q = map(int,input().split())
query = []
for q in range(Q):
a,b,c,d = map(int,input().split())
a-=1
b-=1
query.append((a,b,c,d))
mx = 0
for comb in itertools.combinations_with_replacement([i for i in range(1,M+1)],N):
score = 0
for a,b,c,d in query:
if (comb[b] - comb[a] == c):
score += d
mx = max(score,mx)
print (mx)

D - Floor Function

https://atcoder.jp/contests/abc165/tasks/abc165_d

関数の値はxに対して周期Bで変化することが直感的に分かる。
そこでx=pB+qとおく。(q<B

1
2
floor(Ax/B) = pA+floor(Aq/B)
A*floor(x/B) = pA

となるので、元の関数は

1
floor(Aq/B)

となる。q<Bであるため、この関数はqに対して単調増加である。

よって、NB-1のうち小さい値が答えとなる。

1
2
3
4
A, B, N = map(int,input().split())
x = min(B-1,N)
ans = (A*x)//B - A * (x//B)
print (ans)

E - Rotation Matching

https://atcoder.jp/contests/abc165/tasks/abc165_e

(初期に割り当てられる)参加者番号の差分が1,2,3,4,..となるように対戦場に番号を割り振ることができれば制約を満たすことができる。
そのような割り振り方は、解説pdfの通りに参加者をM//2人のグループと(M+1)//2人の2つのグループに分ければよい。

1
2
3
4
5
N, M = map(int, input().split())
for i in range(M//2):
print(i+1, M-i)
for i in range((M+1)//2):
print (M+1+i,2*M+1-i)

F - LIS on Tree

https://atcoder.jp/contests/abc165/tasks/abc165_f

深さ優先探索とLISの組み合わせで解くことができます。

LISの基本については下記の問題で練習するのが良いでしょう。
ABC006D - トランプ挿入ソート

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
import bisect
import sys
sys.setrecursionlimit(10**6)
INF = 10**10

N = int(input())
A = [0] + list(map(int,input().split()))

adj = [[] for n in range(N+1)]
for n in range(N-1):
u, v = map(int,input().split())
adj[u].append(v)
adj[v].append(u)

ans= [0 for i in range(N+1)]
depth = [-1 for n in range(N+1)]
def dfs(s, d, dp): # 頂点番号、深さ、LIS
depth[s] = d
idx = bisect.bisect_left(dp,A[s])
history = dp[idx] # 巻き戻すために保存
dp[idx] = A[s]
ans[s] = bisect.bisect_left(dp, INF)
for i in adj[s]:
if depth[i]==-1:
dfs(i, d+1, dp)
dp[idx] = history # 探索が終わったら巻き戻す
dp = [INF] * len(A)
dfs(1, 0, dp)
for n in range(1,N+1):
print(ans[n])

関連記事

過去のABC解説記事です。

  • ABC164
    • A-E問題を解いています。
  • ABC163
    • A-D問題を解いています。
  • ABC162
    • A-E問題を解いています。

記事情報

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

ABC145D Knight

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

逆元

はじめに

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

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

問題

https://atcoder.jp/contests/abc145/tasks/abc145_d

この問題では逆元の計算が求められます。

方針

組み合わせの問題として解くことができます。 以下の値を計算すれば良いことになります。

1
2
3
W = X - ((X+Y)//3)
H = Y - ((X+Y)//3)
(W+H)!/(W!H!) % MOD

階乗の部分が巨大になるため、計算途中で剰余の計算を行います。しかしながら、除算が含まれるため逆元と呼ばれる概念を導入する必要があります。

逆元については以下の問題の解説をご覧ください。

ABC034C 散歩

ポイント

  • 移動可能な必要十分条件
    • 移動による座標の変化は(+1,+2)または(+2,+1)で、スタートは(0,0)であるため、X+Yは3の倍数である必要があります。
    • また、(+1,+2)の移動をn回繰り返した場合は(n,2n)に、(+2,+1)の移動をn回繰り返した場合は(2n,n)になることから、2min(X,Y) >= max(X,Y)である必要もあります。

コード

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
MOD = 10**9+7
X, Y = map(int, input().split())
if (X > Y):
X, Y = Y, X
if (X+Y)%3 != 0:
print (0)
exit()
if (2*X < Y):
print (0)
exit()
W = X - ((X+Y)//3)
H = Y - ((X+Y)//3)
mx = 10**6
fact = [1] * (mx+1) # 階乗を格納するリスト
def inv(n): # MODを法とした逆元
return pow(n, MOD-2, MOD)
for i in range(mx):
fact[i+1] = fact[i] * (i+1) % MOD # 階乗を計算
ans = (fact[W+H] * inv(fact[W]) * inv(fact[H])) % MOD # comb(W+H,W) = (W+H)!/(W!H!)
print (ans)

記事情報

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

ABC034C 経路

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

逆元

はじめに

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

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

問題

https://atcoder.jp/contests/abc034/tasks/abc034_c

この問題では逆元の計算が求められます。

方針

組み合わせの問題として解くことができます。 以下の値を計算すれば良いことになります。

((W-1)+(H-1))!/((W-1)!(H-1)!) % MOD

階乗の部分が巨大になるため、計算途中で剰余の計算を行います。しかしながら、除算が含まれるため逆元と呼ばれる概念を導入する必要があります。

逆元とは

amが互いに素である場合、mを法としてax≡1となるようなxが存在します(必要十分条件)。これをmを法とするaの逆元と言います。

逆元は複数存在し得ますが、xに対してさらにmを法とするとただ一つに定まります。この問題では合同式で計算が進むため、逆元は単一の整数と考えて問題はありません。

こちらを参考にさせていただきました。

整数の合同

フェルマーの小定理

pを素数として、pと互いに素な数aについて
a^(p-1)≡1が成立します。これをフェルマーの小定理と呼びます。

逆元と組み合わせて考える

さて、apは互いに素ですので、逆元a^(-1)を考えることができます。つまり、逆元をフェルマーの小定理を使うことで計算できます。
a^(-1)≡a^(p-2)

冪剰余

さて次はa^(p-2) mod pを求めれば良いですが、この問題ではpは巨大であるため、先にa^(p-2)を計算してから剰余を取ろうとすると、計算効率が著しく悪くなります。そこで、計算の最中に随時剰余の計算を行う必要があります。Pythonの場合は、この処理をpow(a,p-2,p)とすることで計算できます。

コード

1
2
3
4
5
6
7
8
9
10
11
12
MOD = 10**9+7
W, H = map(int, input().split())
W-=1 # コードを見やすくする
H-=1 # コードを見やすくする
mx = 2*10**5
fact = [1] * (mx+1) # 階乗を格納するリスト
def inv(n): # MODを法とした逆元
return pow(n, MOD-2, MOD)
for i in range(mx):
fact[i+1] = fact[i] * (i+1) % MOD # 階乗を計算
ans = (fact[W+H] * inv(fact[W]) * inv(fact[H])) % MOD # comb(W+H,W) = (W+H)!/(W!H!)
print (ans)

記事情報

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

Square869120Countest#1E - 散歩 (E869120 and Path Length)

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

累積和

はじめに

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

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

問題

https://atcoder.jp/contests/s8pc-1/tasks/s8pc_1_e

この問題では巨大な数の冪剰余の計算が求められます。

方針

  1. 冪剰余を求める
    • pow(m,n)%MODnが巨大である場合、高速に計算する方法があります。こちらの記事で解説しています。
    • Pythonでは標準で実装されおり、pow(m,n,MOD)とすれば良いです。
  2. 累積和を求める
    • 仮想的な街0を考え、街0から任意の街iまでの距離を事前に計算しておきます(acc[i-1]とする)。そうすることで、任意の街(i,j)の間の距離をabs(acc[i-1]-acc[j-1])のように計算量O(1)で求められます。
    • 標準モジュールのitertools.accumulateが適当でしょう。

コード

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
from itertools import accumulate
MOD = 10**9+7
N, Q = map(int, input().split())
A = list(map(int, input().split()))
C = list(map(int, input().split()))

D = [0]
for n in range(N-1):
d = pow(A[n],A[n+1],MOD) # pow(A[n],A[n+1])%MOD より高速
D.append(d)
acc = list(accumulate(D)) # 累積和

C.insert(0,1) # 始点
C.append(1) # 終点
ans = 0
for q in range(len(C)-1):
ans += abs(acc[C[q+1]-1]-acc[C[q]-1]) % MOD
print (ans%MOD) # 最後のMODを忘れずに

記事情報

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

NTL1B べき乗

はじめに

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

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

問題

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

Pythonでべき乗の計算をする場合は組み込み関数のpowを用いることができます。
ただし、下記のように愚直に実装すると間に合いません。

1
pow(m,n) % MOD

これはpow(m,n)が計算途中でとても巨大な数となってしまい、処理に時間がかかるためです。

しかし、合同式の性質からpow(m,n)計算の過程で剰余を取っても問題はありません。この工夫で計算途中で巨大な数になることを防げるはずです。

powにはそのための呼び出し方法が実装されており、pow(m,n,MOD)のように呼び出すことができます。

詳しくは公式リファレンスを参考にして下さい。なお、Pythonpow繰り返し二乗法で実装されています。

コード

これらの考察から解答はいたってシンプルなコードになります。

1
2
3
MOD = 10**9+7
m, n = map(int, input().split())
print (pow(m,n,MOD))

記事情報

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

NTL1A 素因数分解

はじめに

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

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

問題

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

素因数分解の問題です。試し割り法と呼ばれるアルゴリズムで解くことができます。

試し割り法のアルゴリズム

  • Nのコピーnを用意する。
  • 空の解答リストを用意する。
  • 2~Nの探索リストを作成する
  • 探索リストから数iを取り出し、niで割り切れるだけ割り続けてnを更新する
    • 割るたびに解答リストに追加する

ポイント

  • 探索リストは2~Nではなく2~sqrt(N)としても良い。
    • ただし、終了状態でn=1となっていない場合があるので、最後にnを解答リストに追加する。このnは素数であることが保証されている。

解説

niで割り切れた場合に解答リストに追加を行うが、その場合はiが素数であることは保証されている。その理由は考察する。

仮にiが合成数であった場合、iiより小さい素数のいくつかの積で表すことができる。しかし、アルゴリズムの最中でnに対して割り切れるだけ割っているため、必ずiで割り切ることはできなくなっているはずである。ゆえにiは素数である。

同様の理由でポイントで述べた終了状態のnが素数であることを説明できる。

コード

1
2
3
4
5
6
7
8
9
10
11
12
N = int(input())
n = N
ans = []
for i in range(2,int(N**0.5)+1): # intに変換すること
while(n%i==0):
n //= i # 整数除算(//)を使うこと
ans.append(i)
if n!=1:
ans.append(n)
ans = [str(a) for a in ans]
ans = str(N)+": " + " ".join(ans)
print (ans)

記事情報

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

ABC084D - 2017-like Number

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

エラトステネスの篩

はじめに

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

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

問題

https://atcoder.jp/contests/abc084/tasks/abc084_d

この問題では高速な素数判定法が求められます。そこで、エラトステネスの篩を実装します。

解法1

公式の解説pdfにある通りです。

方針

  1. 素数の表を作る
  2. 2017に似た数の表を作る
  3. 累積和を求める

コード

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
N = 10**6
dp = [1] * (N+1)
dp[0] = dp[1] = 0

for i in range(2,N+1):
if dp[i]:
for j in range(i*i, N+1, i):
dp[j] = 0
like2017 = [0] * (N+1)
for n in range(N+1):
if dp[n]==0:
continue
query = (n+1)//2
if dp[query]==1:
like2017[n] = 1


for n in range(N):
like2017[n+1] += like2017[n]

Q = int(input())
for q in range(Q):
l, r = map(int,input().split())
print (like2017[r]-like2017[l-1])

解法2

2017に似た数のリストを用意し、二分探索を行っても十分に間に合います。実装の複雑さなどを考えても、解法1が良いと思います。

方針

  1. 素数のリストを作る
  2. 2017に似た数のリストを作る
  3. 二分探索を行う
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
import bisect
N = 10**6
dp = [1] * (N+1)
dp[0] = dp[1] = 0
prime = []
for i in range(2,N+1):
if dp[i]:
for j in range(i*i, N+1, i):
dp[j] = 0
for n in range(N+1):
if dp[n]:
prime.append(n)

like2017 = []
for p in prime:
query = (p+1)//2
idx = bisect.bisect_left(prime, query)
if prime[idx] == query:
like2017.append(p)

Q = int(input())
for q in range(Q):
l, r = map(int,input().split())
l = bisect.bisect_left(like2017, l)
r = bisect.bisect_right(like2017, r)
print (r-l)

記事情報

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

ABC065D - Built?

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

プリム法

はじめに

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

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

問題

https://atcoder.jp/contests/abc065/tasks/arc076_b

この問題は最小全域木を求めることで解くことができます。最小全域技の基本を理解するためにはまず下記の問題に取り組むことをオススメします。

GRL2A Minimum Spanning Tree

方針

解説PDFを参考にすると良いでしょう。

  • 最大でN=10**5であるため、愚直に最小全域木を求めることができません。
  • 街と街の間にコストmin(|a-c|,|b-d|)の辺があるのではなく、コスト|a-c|の辺とコスト|b-d|の辺があると考える
  • x,y座標のうちどちらかに着目し、数直線上に街が並んでいると考える。この時、数直線上で隣接している街と街の間の辺さえ考えれば良い。(それらを足し合わせることで、離れている街同士の辺を表現できる)
  • 最後にx座標についての考察で得られた辺の集合と、y座標のそれの和集合(辺の数は2(N-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
33
34
35
36
import heapq
N = int(input())
X = [] # ソートするためのリスト
Y = []
for n in range(N):
x, y = map(int,input().split())
X.append((x, n)) # 座標と街番号
Y.append((y, n))
X.sort() # 座標でソート
Y.sort()
adj = [ [] for v in range(N)]
for n in range(N-1): # 数直線上で隣接している街と街の座標の差分を求める
cost = X[n+1][0] - X[n][0] # まずはx
adj[X[n+1][1]].append((cost, X[n][1])) # (辺のコスト, to街番号)
adj[X[n][1]].append((cost, X[n+1][1]))
cost = Y[n+1][0] - Y[n][0] # yも同様
adj[Y[n+1][1]].append((cost, Y[n][1]))
adj[Y[n][1]].append((cost, Y[n+1][1]))

visited = [0] * N # 訪問フラグ
pq = [] # 優先度付きキュー
for w, t in adj[0]: # 始点はどこでも良いので、0とする
heapq.heappush(pq, (w, t))
visited[0] = 1 # 始点の訪問フラグを立てる

ans = 0
while(pq): # 訪問済みならスキップ
w, t = heapq.heappop(pq)
if visited[t]:
continue
visited[t] = 1 # 訪問フラグを立てる
ans += w # スコアに加算
for w, s in adj[t]: # 隣接する頂点を列挙
if visited[s]==0: # 未訪問なら探索候補としてpqに追加
heapq.heappush(pq, (w, s))
print (ans)

記事情報

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

GRL2A 最小全域木

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

プリム法

プリム法のアルゴリズム解説(wikipedia)

はじめに

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

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

問題

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

この問題は最小全域木を求める問題です。プリム法を用いて解くことができます。

優先度付きキューを使って高速化しました。

コード

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
import heapq
V, E = map(int, input().split()) # 頂点、辺
adj = [ [] for v in range(V)] # 隣接リスト
for e in range(E):
s, t, w = map(int, input().split())
adj[s].append((t,w))
adj[t].append((s,w))

visited = [0] * V # 訪問フラグ
pq = [] # 優先度付きキュー
for t, w in adj[0]: # 始点はどこでも良いので、0とする
heapq.heappush(pq, (w, t))
visited[0] = 1 # 始点の訪問フラグを立てる

ans = 0
while(pq):
w, t = heapq.heappop(pq)
if visited[t]: # 訪問済みならスキップ
continue
visited[t] = 1 # 訪問フラグを立てる
ans += w # スコアに加算
for s, w in adj[t]: # 隣接する頂点を列挙
if visited[s]==0: # 未訪問なら探索候補としてpqに追加
heapq.heappush(pq, (w, s))
print (ans)

記事情報

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

ABC164

はじめに

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

A - Sheep and Wolves

https://atcoder.jp/contests/abc164/tasks/abc164_a

S = Wの場合を処理し間違えないようにしましょう。

1
2
3
4
5
S, W = map(int,input().split())
if S <= W:
print ('unsafe')
else:
print ('safe')

B - Battle

https://atcoder.jp/contests/abc164/tasks/abc164_b

除算をしても良いし、数が小さいのでシミュレーションをしても良いです。

1
2
3
4
5
6
7
8
9
10
a,b,c,d = map(int,input().split())
while(1):
c -= b
if (c<=0):
print ('Yes')
break
a -= d
if (a<=0):
print ('No')
break

C - gacha

https://atcoder.jp/contests/abc164/tasks/abc164_c

Pythonの場合setを使うと楽です。

1
2
3
4
5
N = int(input())
ls = []
for n in range(N):
ls.append(input())
print (len(set(ls)))

D - Multiple of 2019

https://atcoder.jp/contests/abc164/tasks/abc164_d

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

解法1

2019で割った余りがxである文字列が何通りかをサイズ2019のリストに記録していきます。

具体例として、2019の倍数である18171が入力となった場合について考えます。

リストcntは以下のように遷移します。(値が0の要素は省略)

1
2
3
4
5
6
7
8
9
10
# 1文字目の入力
cnt[1] = 1
# 2文字目の入力
cnt[18] = 1, cnt[8] = 1
# 3文字目の入力
cnt[181] = 1, cnt[81] = 1, cnt[1] = 1
# 4文字目の入力
cnt[1817] = 1, cnt[817] = 1, cnt [17] = 1, cnt[7] = 1
# 5文字目の入力
cnt[0] = 1, cnt[8171] = 1, cnt[171] = 1, cnt[71] = 1, cnt[1] = 1

cnt[0]0以外の値が入るごとに、その値をansに足していけば良いです。

Pythonだと計算量がギリギリなので、PyPy3を使いました。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
N = 2019
cnt = [0] * N
S = input()
ans = 0
for s in S:
s = int(s)
new = [0] * N
for n in range(N):
idx = (10 * n + s) % N
new[idx] += cnt[n]
cnt = new
cnt[s] += 1
ans += cnt[0]
print (ans)

解法2

公式の解説にしたがって解きました。

右端を含む文字列集合と文字列0の和集合を考えます。
例えば入力が12345であった場合、以下のような集合です。
集合A

1
{12345, 2345, 345, 45, 5, 0}

この集合からL > Rとなるように二つの数を選び、L-Rを適切に10^nで割ってあげること(シフトするイメージ)で、Sの部分文字列を列挙考えることができます。

1
2
2345 - 45 = 2300
2300 / 100 = 23

ところで201910は互いに素なので、2019を法として、

1
2
3
     L/(10^n) - R/(10^n) ≡ 0
<=> L - R ≡ 0
<=> L ≡ R

が成り立ちます。そのため、2019を法とした時に同じ値になる数を数え上げ、ペアの作り方をシグマ計算すれば良いです。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
S = input()
MOD = 2019
cnt = [0]*MOD
cur = 0 # 現在考えている部分文字列
cnt[cur] = 1 # 部分文字列'0'も作れたと考える
d = 1 # 位を表す
for s in S[::-1]: # 右から作る
cur += int(s)*d # 新たな桁を追加
cur %= MOD
cnt[cur] += 1
# 後処理
d *= 10 # 位を上げる
d %= MOD # ただし数が巨大になるため、計算量を減らすために剰余をとる
ans = 0
for c in cnt:
ans += c*(c-1)//2
print (ans)

E - Two Currencies

https://atcoder.jp/contests/abc164/tasks/abc164_e

A,Nの最大値は50なので、銀貨は2500枚あれば十分です。つまり、(現在の頂点, 所持している銀貨の枚数)の状態数は最大で50*2500なので、ダイクストラ法を用いて解くことができます。

公式の解説にしたがって、状態(現在の頂点, 所持している銀貨の枚数)を要素数2のリストで表現しています。これらの組み合わせを考えて要素数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
33
34
35
36
37
38
39
40
41
import heapq
INF = 10**20

N, M, S = map(int, input().split())
adj = [[] for _ in range(N)]
for m in range(M):
u,v,a,b = map(int, input().split())
u -= 1
v -= 1
adj[u].append((v,a,b))
adj[v].append((u,a,b))

C = []
D = []
for n in range(N):
c, d = map(int, input().split())
C.append(c)
D.append(d)

M = 2500
S = min(S, M-1) # 2500枚あれば十分
dist = [[INF]*M for _ in range(N)] # dist[都市][銀貨の枚数]
dist[0][S] = 0
pq = [(0,S,0)] # (時間、銀貨の枚数、都市)

while pq:
d, sil, node = heapq.heappop(pq)
if dist[node][sil] < d: # 探索の対象にする必要があるか確認
continue
# 銀貨に交換したとして、上限を超えないか、探索の必要があるか
if sil+C[node] < M and d+D[node] < dist[node][sil+C[node]]:
dist[node][sil+C[node]] = d + D[node] # 時間の更新
heapq.heappush(pq, (d + D[node], sil+C[node] ,node))
for next, a, b in adj[node]:
# 銀貨が足りるか、探索の必要があるか
if sil >= a and d + b < dist[next][sil-a]:
dist[next][sil-a] = d + b
heapq.heappush(pq, (d + b, sil-a ,next))

for i in range(1,N):
print(min(dist[i])) # 残っている銀貨の枚数は問わない

関連記事

過去のABC解説記事です。

  • ABC163
    • A-D問題を解いています。
  • ABC162
    • A-E問題を解いています。

記事情報

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