Day 5.
It is a really hard problem for me, but I manage to write codes and grasp the meaning of them.
There are 3 answers. Each of them has own perspective. 1st one is Digit DP, 2nd one is matrix exponentiation, last one is Doubling.
Code is below.
1st one.
#1st solution
N, B, K = map(int,input().split())
C = list(map(int, input().split()))
#入力を準備する。
MOD = 10**9 + 7
dp = [[0 for b in range(B)] for n in range(N) ]
#動的計画法のかごを用意する
for j in range(K):
dp[0][C[j]%B] += 1
#最初の条件(状態)、1桁目
for i in range(N-1):
for j in range(B):
for k in range(K):
new = (10 * j + C[k]) % B
dp[i+1][new] += dp[i][j]
dp[i+1][new] %= MOD
print(dp[N-1][0])
- 桁ごとに考える問題。
- 今回のように、いくつもの要素があって、その組み合わせ、または、数(数も離散的な一つ一つの塊として見れる)その組み合わせを順々にいろいろ計算したいと気に、動的計画法が使える。
- 詳しくはこちら。
2nd one.
#2nd solution
N, B, K = map(int,input().split())
C = list(map(int, input().split()))
#入力を準備する。
MOD = 10**9 + 7
import numpy as np
A = [[0 for i in range(B)] for j in range(B)]
#隣接行列のかごを用意する
for i in range(B):
for k in range(K):
A[(i*10 + C[k])%B][i] += 1
#隣接行列を用意する。行と列に遷移前後のBでの余りを保持
#その組み合わせが、隣接行列になる。
def bit(n):
bit_digit = []
while n > 0:
if n%2 == 1:
bit_digit.append(1)
else:
bit_digit.append(0)
n //= 2
#一桁目は2で割ったあまり
#二桁目は4で割って2余るか
return bit_digit
#ここは正しい
def Dot(M1,M2,n):
#行列の積を定義する。
M = [[0] * n for _ in range(n)]
for i in range(n):
for j in range(n):
for k in range(n):
M[i][j] += M1[i][k] * M2[k][j]
M[i][j] %= MOD
#足し合わせる、MODで割る
return M
bit_N = bit(N)
#N桁なので、N個の隣接行列をかければよい
# これは最初が0だから。
#〇〇桁なので、べき乗の準備をする。
#Nを2進数に直す
bit_length = len(bit_N)
def Power_Matrix(M3,k,n):
P = [[0] * n for _ in range(n)]
for i in range(n):
P[i][i] = 1
for j in range(k):
#M3の(j)乗の部分
if bit_N[j]==1:
P = Dot(P,M3,n)
#二進法で桁が1なら×
M3 = Dot(M3, M3, n)
#かける行列を二乗する。
return P
Answer = Power_Matrix(A,bit_length,B)
print(Answer[0][0])
- Fibonacci数列みたいに、足し方、隣接行列が決まっているものは、工夫できる。
- 隣接行列を最初にべき乗で求めていく。
- こういう 10 = 2 + 8, 30 = 2 + 4 + 8 + 16のような分解を利用して、べき乗を処理していく(掛けていく)のを、繰り返し二乗法という。
3rd one.
#3rd solution
N, B, K = map(int,input().split())
C = list(map(int, input().split()))
#入力をとる
MOD = 10**9 + 7
LOG = 64
def Multiply(list1,list2,number):
res = [0] * B
for i in range(B):
for j in range(B):
res[(i * number + j) % B] += list1[i] * list2[j]
res[(i * number + j) % B] %= MOD
return res
#リスト同士の掛け算を定義する。
#Numberによって、掛け算の仕方が変化する。
#リスト2つとNumberを変数にとる関数
ten = [10] * LOG
for i in range(1, LOG):
ten[i] = (ten[i-1] * ten[i-1]) %B
#これで、10のべき乗の余りのリストが出来る。
#順番に、1, 10 , 100, 1000 ,....
Matrix = [[0] * B for _ in range(LOG)]
#2**n桁の余りの行列のかごを用意する
for k in range(K):
Matrix[0][C[k] % B ] += 1
#1桁目はC[k]%Bを割り当てていく
for i in range(1,LOG):
Matrix[i] = Multiply(Matrix[i-1], Matrix[i-1], ten[i-1])
#Matrixのi個目と、i-1個の階差行列がten[i-1]、というイメージ
#このmatrixは、2のi乗桁上がると、何通りになるかを掛ける行列
answer = [0] * B
answer[0] = 1
for i in range(LOG):
if N &(1<<i):
answer = Multiply(answer, Matrix[i], ten[i])
#2のi乗の桁が1なら、10進法の2のi乗桁分を足す
#そのために、MatrixとAnswerの積(さっき定義した)をかける
print(answer[0])
#あまり0の部分を印刷する。
- 2ndまでは、遷移すること、変化させていく方法に着目していた。
- でも、行列同士の掛け算は、O(B^3)かかるのでシンドイ。
- 今回は、2桁と2桁の完成形同士の合成、というように、すでに出来上がったものを組み合わせて、4桁のような、大きなものを作る
- これが、O(B^2)で出来てうれしい。
- このように、2**i回目の結果から2**(i+1)回目の結果を得る方法を、ダブリングという。
- 高速フーリエ変換とか、きたまさ法、というのがこれに使えるらしいが、今は踏み込まないことにする。
バイバイ!