Пітон, 1,291 1,271 1225 байт
Як зазначив Мартін, цю проблему можна значною мірою звести до його чудового виклику гумками . Використовуючи термінологію цього виклику, ми можемо взяти за другий набір цвяхів точки перетину між колами на межі вкладеної області:
В якості гумки ми можемо взяти будь-який шлях P між двома кінцевими точками, який проходить всередині закритої області. Тоді ми можемо звернутися до вирішення проблеми з резиновою стрічкою, щоб створити (локально) мінімальний шлях. Завдання, звичайно, знайти такий шлях Р , а точніше - знайти достатню кількість шляхів, щоб принаймні один із них створив глобально мінімальний шлях (зауважте, що в першому тестовому випадку нам потрібно хоча б один шлях до охоплюють усі можливості, а у другому тестовому випадку не менше двох.)
Наївним підходом було б просто спробувати всі можливі шляхи: для кожної послідовності сусідніх (тобто пересічних) кіл, що з'єднує дві кінцеві точки, проведіть шлях по їх центрам (коли два кола перетинаються, відрізок між їх центрами завжди знаходиться в межах їх об'єднання .) Хоча цей підхід технічно правильний, він може призвести до смішно великої кількості шляхів. Хоча я міг вирішити перший тестовий випадок, використовуючи цей підхід за кілька секунд, другий зайняв назавжди. Тим не менш, ми можемо взяти цей метод як вихідний пункт і спробувати мінімізувати кількість шляхів, які ми маємо протестувати. Ось що далі.
Ми будуємо наші контури, здійснюючи в основному пошук на глибині на графіку кіл. Ми шукаємо спосіб усунути потенційні напрямки пошуку на кожному кроці пошуку.
Припустимо, що в якийсь момент ми знаходимося в колі А , у якого є два сусідніх кола В і С , які також сусідять один з одним. Ми можемо дістатися від А до С , відвідавши В (і навпаки), тому ми можемо подумати, що відвідувати як В, так і С безпосередньо з А не потрібно. На жаль, це неправильно, як показано на цій ілюстрації:
Якщо точки на ілюстрації є двома кінцевими точками, ми можемо побачити, що, переходячи від А до С через В, ми отримуємо довший шлях.
Як щодо цього: якщо ми тестуємо обидва рухи A → B і A → C , тестувати A → B → C або A → C → B неважливо , оскільки вони не можуть призвести до коротших шляхів. Неправильно знову:
Справа в тому, що використання суто аргументів на основі суміжності не збирається це скорочувати; ми також повинні використовувати геометрію задачі. Два вищезазначені приклади мають спільне (а також другий тестовий випадок у більшому масштабі) - це те, що у закритій області є "дірка". Це проявляється в тому, що деякі точки перетину на межі - наші «цвяхи» - знаходяться в трикутнику △ ABC , вершини якого є центрами кіл:
Коли це станеться, є ймовірність, що перехід від A до B і від A до C призведе до різних шляхів. Що ще важливіше, якщо цього не відбувається (тобто якщо не було розриву між A , B і C ), тоді гарантується, що всі шляхи, починаючи з A → B → C і з A → C , інакше еквівалентні, приведуть в тому ж локально мінімального шляху, отже , якщо ми відвідуємо B ми не повинні також відвідати C безпосередньо з A .
Це призводить нас до нашого способу усунення: Коли ми знаходимось у колі A , ми зберігаємо список H сусідніх кіл, які ми відвідали. Цей список спочатку порожній. Ми відвідуємо сусіднє коло B, якщо в усіх трикутників, утворених центрами A , B і будь-якого з кіл у H, що примикають до B, є якісь "цвяхи" . Цей метод різко зменшує кількість шляхів, які ми маємо протестувати, до лише 1 у першому тестовому випадку та 10 до другого.
Ще кілька приміток:
Можна зменшити кількість стежок, які ми перевіряємо, ще більше, але цей метод досить хороший для масштабу цієї проблеми.
Я використав алгоритм від свого рішення до виклику резинки. Оскільки цей алгоритм є поступовим, його можна досить легко інтегрувати в процес пошуку шляху, так що ми мінімізуємо шлях, як ми йдемо далі. Оскільки багато шляхів поділяють початковий сегмент, це може значно підвищити ефективність, коли у нас є багато шляхів. Це також може зашкодити продуктивності, якщо набагато більше тупиків, ніж дійсних шляхів. Так чи інакше, для даних тестових випадків виконання алгоритму для кожного шляху окремо досить добре.
Є один крайній випадок, коли це рішення може бути невдалим: Якщо будь-яка точка на межі є точкою перетину двох дотичних кіл, то за деяких умов результат може бути неправильним. Це пов’язано з тим, як працює алгоритм з резиновою стрічкою. З деякими модифікаціями можна впоратися і з цими справами, але, пекло, це вже досить довго.
# 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])
Введення задається через змінну I
у вигляді набору кортежів, ((x, y), r)
де (x, y)
є центр кола і r
його радіус. Значення повинні бути float
s, а не int
s. Результат друкується в STDOUT.
Приклад
# 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. ]]