Pitone, 1.291 1.271 1.225 byte
Come ha notato Martin, questo problema può essere ampiamente ridotto alla sua eccellente sfida con l'elastico . Usando la terminologia di quella sfida, possiamo prendere come seconda serie di chiodi i punti di intersezione tra i cerchi sul confine dell'area chiusa:
Come elastico possiamo prendere qualsiasi percorso P tra i due punti finali che corre all'interno dell'area chiusa. Possiamo quindi invocare una soluzione al problema dell'elastico per produrre un percorso (localmente) minimo. La sfida è, ovviamente, trovare un tale percorso P , o più precisamente, trovare abbastanza percorsi in modo che almeno uno di essi produca il percorso globalmente minimo (si noti che nel primo caso di prova abbiamo bisogno di almeno un percorso per coprire tutte le possibilità, e nel secondo caso di test almeno due.)
Un approccio ingenuo sarebbe quello di provare tutti i possibili percorsi: per ogni sequenza di cerchi adiacenti (cioè intersecanti) che collega i due punti finali, prendere il percorso lungo i loro centri (quando due cerchi si intersecano, il segmento tra i loro centri è sempre all'interno della loro unione .) Sebbene questo approccio sia tecnicamente corretto, può portare a un numero ridicolmente elevato di percorsi. Mentre sono stato in grado di risolvere il primo caso di test usando questo approccio in pochi secondi, il secondo ha impiegato un'eternità. Tuttavia, possiamo prendere questo metodo come punto di partenza e cercare di ridurre al minimo il numero di percorsi che dobbiamo testare. Questo è ciò che segue.
Costruiamo i nostri percorsi eseguendo fondamentalmente una ricerca approfondita sul grafico dei cerchi. Stiamo cercando un modo per eliminare potenziali direzioni di ricerca in ogni fase della ricerca.
Supponiamo che ad un certo punto ci troviamo in un cerchio A , che ha due cerchi adiacenti B e C , anch'essi adiacenti l'uno all'altro. Possiamo andare da A a C visitando B (e viceversa), quindi potremmo pensare che visitare B e C direttamente da A non sia necessario. Sfortunatamente, questo è sbagliato, come mostra questa illustrazione:
Se i punti nell'illustrazione sono i due punti finali, possiamo vedere che passando da A a C attraverso B otteniamo un percorso più lungo.
Che ne dici di questo: se stiamo testando entrambe le mosse A → B e A → C , non è necessario testare A → B → C o A → C → B , poiché non possono comportare percorsi più brevi. Sbagliato di nuovo:
Il punto è che l'uso di argomenti basati esclusivamente sull'adiacenza non lo taglierà; dobbiamo usare anche la geometria del problema. Ciò che i due esempi precedenti hanno in comune (così come il secondo caso di prova su una scala più ampia) è che c'è un "buco" nell'area chiusa. Si manifesta nel fatto che alcuni dei punti di intersezione sul confine - i nostri "chiodi" - sono all'interno del triangolo △ ABC i cui vertici sono i centri dei cerchi:
Quando ciò accade, c'è la possibilità che passare da A a B e da A a C provocherà percorsi diversi. Ancora più importante, quando non accade (cioè se non ci fosse uno spazio tra A , B e C ), è garantito che tutti i percorsi che iniziano con A → B → C e con A → C e che sono altrimenti equivalenti risulteranno nello stesso percorso localmente minima, quindi se visitiamo B non abbiamo bisogno di visitare anche C direttamente da a .
Questo ci porta al nostro metodo di eliminazione: quando siamo in un cerchio A , manteniamo un elenco H dei cerchi adiacenti che abbiamo visitato. Questo elenco è inizialmente vuoto. Visitiamo un cerchio adiacente B se ci sono dei "chiodi" in tutti i triangoli formati dai centri di A , B e uno qualsiasi dei cerchi in H adiacenti B . Questo metodo riduce drasticamente il numero di percorsi che dobbiamo testare a solo 1 nel primo caso di test e 10 nel secondo.
Qualche nota in più:
È possibile ridurre ulteriormente il numero di percorsi che testiamo, ma questo metodo è abbastanza buono per la portata di questo problema.
Ho usato l'algoritmo dalla mia soluzione alla sfida dell'elastico. Poiché questo algoritmo è incrementale, può essere facilmente integrato nel processo di ricerca del percorso, in modo da ridurre al minimo il percorso lungo il percorso. Poiché molti percorsi condividono un segmento iniziale, questo può migliorare significativamente le prestazioni quando abbiamo molti percorsi. Può anche danneggiare le prestazioni se ci sono molti più vicoli ciechi rispetto a percorsi validi. In entrambi i casi, per i casi di test forniti l'esecuzione dell'algoritmo per ciascun percorso separatamente è abbastanza buona.
C'è un caso limite in cui questa soluzione potrebbe non riuscire: se uno qualsiasi dei punti sul confine è il punto di intersezione di due cerchi tangenti, in alcune condizioni il risultato può essere errato. Ciò è dovuto al modo in cui l'algoritmo elastico funziona. Con alcune modifiche è possibile gestire anche questi casi, ma, diavolo, è già abbastanza lungo.
# First test case
I={((32.,42.),64.),((112.,99.),59.),((141.,171.),34.),((157.,191.),28.),((177.,187.),35.),((244.,168.),57.),((289.,119.),20.),((299.,112.),27.),((354.,59.),58.),((402.,98.),23.),((429.,96.),29.),((424.,145.),34.),((435.,146.),20.),((455.,204.),57.),((430.,283.),37.),((432.,306.),48.),((445.,349.),52.),((424.,409.),59.),((507.,468.),64.)}
# Second test case
#I={((32.,42.),64.),((112.,99.),59.),((141.,171.),34.),((157.,191.),28.),((177.,187.),35.),((244.,168.),57.),((289.,119.),20.),((299.,112.),27.),((354.,59.),58.),((402.,98.),23.),((429.,96.),29.),((424.,145.),34.),((435.,146.),20.),((455.,204.),57.),((430.,283.),37.),((432.,306.),48.),((445.,349.),52.),((424.,409.),59.),((507.,468.),64.),((180.,230.),39.),((162.,231.),39.),((157.,281.),23.),((189.,301.),53.),((216.,308.),27.),((213.,317.),35.),((219.,362.),61.),((242.,365.),42.),((288.,374.),64.),((314.,390.),53.),((378.,377.),30.),((393.,386.),34.)}
from numpy import*
V=array;X=lambda u,v:u[0]*v[1]-u[1]*v[0];L=lambda v:dot(v,v)
e=V([511]*2)
N=set()
for c,r in I:
for C,R in I:
v=V(C)-c;d=L(v)
if d:
a=(r*r-R*R+d)/2/d;q=r*r/d-a*a
if q>=0:w=V(c)+a*v;W=V([-v[1],v[0]])*q**.5;N|={tuple(t)for t in[w+W,w-W]if all([L(t-T)>=s**2-1e-9 for T,s in I])}
N=map(V,N)
def T(a,b,c,p):H=[X(p-a,b-a),X(p-b,c-b),X(p-c,a-c)];return min(H)*max(H)>=0
def E(a,c,b):
try:d=max((X(n-a,b-a)**2,id(n),n)for n in N if T(a,b,c,n)*X(n-b,c-b)*X(n-c,a-c))[2];A=E(a,c,d);B=E(d,c,b);return[A[0]+[d]+B[0],A[1]+[sign(X(c-a,b-c))]+B[1]]
except:return[[]]*2
def P(I,c,r,A):
H=[];M=[""]
if L(c-e)>r*r:
for C,R in I:
if L(C-c)<=L(r+R)and all([L(h-C)>L(R+s)or any([T(c,C,h,p)for p in N])for h,s in H]):v=V(C);H+=[(v,R)];M=min(M,P(I-{(C,R)},v,R,A+[v]))
return M
A+=[e]*2;W=[.5]*len(A)
try:
while 1:
i=[w%1*2or w==0for w in W[2:-2]].index(1);u,a,c,b,v=A[i:i+5];A[i+2:i+3],W[i+2:i+3]=t,_=E(a,c,b);t=[a]+t+[b]
for p,q,j,k in(u,a,1,i+1),(v,b,-2,i+len(t)):x=X(q-p,c-q);y=X(q-p,t[j]-q);z=X(c-q,t[j]-q);d=sign(j*z);W[k]+=(x*y<=0)*(x*z<0 or y*z>0)*(x!=0 or d*W[k]<=0)*(y!=0 or d*W[k]>=0)*d
except:return[sum(L(A[i+1]-A[i])**.5for i in range(len(A)-1)),id(A),A]
print V(P(I,e*0,0,[e*0]*2)[2][1:-1])
L'input è dato attraverso la variabile I
come un insieme di tuple ((x, y), r)
dove (x, y)
è il centro del cerchio ed r
è il suo raggio. I valori devono essere float
s, non int
s. Il risultato viene stampato su STDOUT.
Esempio
# First test case
I={((32.,42.),64.),((112.,99.),59.),((141.,171.),34.),((157.,191.),28.),((177.,187.),35.),((244.,168.),57.),((289.,119.),20.),((299.,112.),27.),((354.,59.),58.),((402.,98.),23.),((429.,96.),29.),((424.,145.),34.),((435.,146.),20.),((455.,204.),57.),((430.,283.),37.),((432.,306.),48.),((445.,349.),52.),((424.,409.),59.),((507.,468.),64.)}
[[ 0. 0. ]
[ 154.58723733 139.8329183 ]
[ 169.69950891 152.76985495]
[ 188.7391093 154.02738541]
[ 325.90536774 109.74141936]
[ 382.19108518 109.68789517]
[ 400.00362897 120.91319495]
[ 511. 511. ]]