https://atcoder.jp/contests/soundhound2018-summer-qual/tasks/soundhound2018_summer_qual_d
Lorsque j'ai résolu cette question passée d'AtCoder, j'ai pu faire AC en utilisant la méthode Dyxtra, mais avec Python (PyPy), le délai d'exécution (2000ms) est à peine atteint. Lorsque j'ai recherché des soumissions passées, j'ai trouvé une réponse rapide de 789 ms alors qu'il y en avait beaucoup plus de 1000 ms.
https://atcoder.jp/contests/soundhound2018-summer-qual/submissions/7721902
Il fait quelque chose comme le calcul de l'itinéraire le plus court par priorité à la largeur pour rechercher un arbre, mais si le graphique a un chemin fermé au lieu d'un arbre, le temps de calcul semble être $ O (V ^ 2) $ (s'il s'agit d'un arbre). $ O (V) = O (E) $). Cependant, la réponse est en fait rapide. Considérant la possibilité que le montant du calcul soit mal estimé, j'ai comparé les trois méthodes suivantes.
Référence: <a href = https: //ja.wikipedia.org/wiki/%E3%83%80%E3%82%A4%E3%82%AF%E3%82%B9%E3%83%88%E3 % 83% A9% E6% B3% 95> Pseudo code article Wikipedia
Si l'on considère uniquement le temps de calcul, dijkstra1 </ b> semble être le plus rapide dans de nombreux cas.
Utilisez un graphe concaténé non orienté ($ V $ pour les sommets, $ E $ pour les côtés, pas de côtés multiples, pas d'auto-boucle). Tout d'abord, comme le montre la figure ci-dessous, en créant une route unique qui traverse tous les points une fois dans l'ordre à partir du point de départ, la connexion est garantie (l'ordre des sommets le long du chemin est aléatoire). Les autres côtés sont étirés au hasard entre des sommets qui n'ont pas encore de côtés. Le poids des côtés est déterminé aléatoirement de 1 $ à 10 $ ^ 7 $ pour chaque côté.
$V \in \{ 10^3,~ 10^4,~ 10^5,~ 10^6,~ 10^7 \} $ $E \in \{ 10^3,~ 10^4,~ 10^5,~ 3\cdot 10^5,~ 10^6,~ 10^7 \} $ (La raison pour laquelle j'ai mis $ 3 \ cdot 10 ^ 5 $ dans le nombre de côtés est parce que j'ai assumé les restrictions de la concurrence pro)
Le temps a été mesuré pour $ (V, E) $ satisfaisant $ V-1 \ leqq E \ leqq \ frac {V (V-1)} {2} $.
worstcase.py
def dijkstra0(a, in_connect_list, v, money_kind):
out_shortest_list = [10**15 for i in range(v)] #Distance du sommet a
out_shortest_list[a] = 0
searching_list = [a]
while searching_list != []:
new_search_list = []
for i in searching_list:
for j in in_connect_list[i]:
c = j[0]
p = j[money_kind]
if out_shortest_list[c] > p + out_shortest_list[i]:
out_shortest_list[c] = p + out_shortest_list[i]
new_search_list.append(c)
searching_list = list(set(new_search_list)) #Changement du code d'origine
return out_shortest_list
def dijkstra1(s, M):
D = [10**15] * v #Distance des sommets s
D[s] = 0
V = [0] * v #D à ce sommet[i]Si est déterminée comme étant la distance la plus courte 1
Q = [] #File d'attente de priorité
for v in range(v):
heapq.heappush(Q, [D[v], v])
le = len(Q)
while le > 0:
q = heapq.heappop(Q)
u = q[1]
du = q[0]
if V[u] == 0:
V[u] = 1
le -= 1
for i in range(len(M[u])):
v = M[u][i][0]
luv = M[u][i][1]
if V[v] == 0:
alt = du + luv
if D[v] > alt:
D[v] = alt
heapq.heappush(Q, [alt, v])
return D
def dijkstra2(s, M):
D = [10**15] * v #Distance des sommets s
D[s] = 0
Q = [i for i in range(v)]
while len(Q) > 0:
mi_ind = -1
mi = 10**15
for i in Q:
if D[i] < mi:
mi = D[i]
mi_ind = i
Q.pop(Q.index(mi_ind))
for i in range(len(M[mi_ind])):
v = M[mi_ind][i][0]
luv = M[mi_ind][i][1]
D[v] = min(D[v], D[mi_ind] + luv)
return D
from random import randint, random, sample
import heapq
import time
V = [10**3, 10**4, 10**5, 10**6, 10**7]
E = [10**3, 10**4, 10**5, 3 * 10**5, 10**6, 10**7]
for v in V:
for e in E:
if v-1 <= e <= v*(v-1)//2: #Conditions de concaténation et pas de côtés multiples
T = []
for _ in range(10): #Prendre la moyenne mesurée 10 fois
N_shuf = [0] + sample([i for i in range(1, v)], v-1)
M = [[] for i in range(v)]
A = [min(N_shuf[i],N_shuf[i+1]) * v
+ max(N_shuf[i],N_shuf[i+1]) for i in range(v-1)]
while len(A) < e:
e_add = e - len(A)
ADD = [0] * e_add
i = 0
while i < e_add:
v0 = randint(0, v-1)
v1 = randint(0, v-1)
if v0 != v1:
ADD[i] = min(v0,v1) * v + max(v0,v1)
i += 1
A += ADD
A = list(set(A)) #Supprimer plusieurs côtés
for ii in A:
i = ii // v
j = ii % v
M[i].append([j, randint(1, 10**7)])
M[j].append([i, randint(1, 10**7)])
t0 = time.time()
D = dijkstra0(0, M, v, 1)
# D = dijkstra1(0, M)
# D = dijkstra2(0, M)
t1 = time.time() - t0
T.append(t1)
print(v, e, sum(T) / 10)
La moyenne de 10 fois, l'unité était de secondes et 2 nombres valides.
0.00060 | 0.0074 | 0.14 | 0.40 | |||
0.0069 | 0.29 | 0.96 | 3.1 | 25 | ||
0.13 | 1.0 | 4.4 | 53 | |||
1.6 | 75 | |||||
18 |
0.0016 | 0.0059 | 0.070 | 0.23 | |||
0.020 | 0.16 | 0.41 | 1.1 | 8.9 | ||
0.36 | 1.3 | 2.6 | 15 | |||
4.4 | 37 | |||||
53 |
0.017 | 0.023 | 0.11 | 0.29 | |||
1.9 | 2.1 | 2.2 | 3.1 | 14 | ||
180 | - | - | - | |||
- | - | |||||
- |
\ -: Non mesuré car cela a pris plus de quelques centaines de secondes
La méthode Dyxtra la plus basique est évidemment plus lente à mesure que $ V $ augmente. Cependant, la méthode utilisant dijkstra0 </ b> et la file d'attente prioritaire ( dijkstra1 </ b>) ne semble pas faire une grande différence dans la plage de la table. Si les conditions sont susceptibles d'être dans une compétition pro ($ V \ leqq 10 ^ 5, ~ E \ leqq 3 \ cdot 10 ^ 5 $), soit l'une passera, soit dans certains cas dijkstra0 </ b> sera meilleure. Il semble également rapide (les comparaisons de temps sont données dans le tableau ci-dessous). Une autre caractéristique de dijkstra0 </ b> est que si vous corrigez $ E $ et augmentez $ V $, cela peut être plus rapide. Apparemment, dijkstra0 </ b> est bon pour les graphes clairsemés et arborescents (= $ V $ est grand mais $ E $ est petit). S'il est proche d'un arbre, ce sera presque $ O (V) $.
Tableau ci-dessous: Ratio de temps requis ( dijkstra0 </ b> / dijsktra1 </ b>)
0.38 | 1.3 | 2.0 | 1.7 | |||
0.35 | 1.8 | 2.4 | 2.8 | 2.8 | ||
0.37 | 0.82 | 1.7 | 3.6 | |||
0.36 | 2.0 | |||||
0.34 |
Le graphique suivant peut être considéré comme le pire des cas de dijkstra0 </ b>. Le nombre de mises à jour de la distance la plus courte est $ (V-1) $ fois pour le sommet supérieur gauche, $ (V-2) $ $ fois pour celui de droite, ..., $ 1 $ pour le sommet supérieur droit. Puisque le nombre total de mises à jour est de $ \ frac {V (V-1)} {2} $ fois, le temps de calcul augmentera presque proportionnellement à $ V ^ 2 $. Réécrivez le code autre que la partie fonction de la méthode Dyxtra comme suit, $ V \ in \ {10 ^ 3, ~ 5 \ cdot 10 ^ 3, ~ 10 ^ 4, ~ 5 \ cdot 10 ^ 4 \} $ Je l'ai comparé avec.
worstcase.py
from random import randint, random, sample
import heapq
import time
for v in [1000, 5000, 10000, 50000]:
T = []
for _ in range(10):
N_shuf = [0] + sample([i for i in range(1, v)], v-1)
M = [[] for i in range(v)]
for i in range(1, v):
M[0].append([N_shuf[i], 10**7 - i * 2])
M[N_shuf[i]].append([0, 10**7 - i * 2])
for i in range(1, v-1):
M[N_shuf[i+1]].append([N_shuf[i], 1])
M[N_shuf[i]].append([N_shuf[i+1], 1])
t0 = time.time()
D = dijkstra0(0, M, v, 1)
# D = dijkstra1(0, M)
# D = dijkstra2(0, M)
t1 = time.time() - t0
T.append(t1)
print(v, sum(T) / 10)
dijkstra0 | dijkstra1 | dijkstra2 | ||
---|---|---|---|---|
1000 | 1997 | 0.11 | 0.0024 | 0.017 |
5000 | 9997 | 3.1 | 0.016 | 0.45 |
10000 | 19997 | 12 | 0.033 | 1.8 |
50000 | 99997 | 740 | 0.25 | 48 |
Encore une fois, dans dijkstra0 </ b>, si $ V $ est multiplié par 10, le temps de calcul sera de 100 à 200 fois (c'est également le cas avec dijkstra2 </ b>). Le résultat est complètement différent. dijkstra1 </ b>, qui est $ O ((E + V) \ log {V}) $, est toujours stable et rapide. Il peut y avoir des cas où ce pire cas n'est pas inclus dans le cas de test comme dans le problème au début, mais il semble préférable d'utiliser dijkstra1 </ b> pour les professionnels de la compétition. Dans le cas de la limite de temps, il vaut mieux y faire face en mettant en œuvre une implémentation rapide comprenant des parties autres que la méthode Dyxtra.
--Lorsque la recherche de priorité de largeur pour les arbres est utilisée pour les graphiques avec des chemins fermés, le temps de calcul est $ O (V ^ 2)
--2020 / 04/20 Changement du titre de l'article (ancien titre: O (V ^ 2) est plus rapide que Dyxtra? Recherche de priorité de largeur pour les graphiques avec des chemins fermés), partiellement révisé
Recommended Posts