Bu soruna yaklaşmak için, bir tamsayı programlama çerçevesi kullanırdım ve üç set karar değişkeni tanımlardım:
- x_ij : Su konumunda (i, j) bir köprü kurup kurmayacağımızı gösteren ikili gösterge değişkeni.
- y_ijbcn : Su konumunun (i, j) b adasını c adasına bağlayan n ^ inci konum olup olmadığına dair ikili bir gösterge.
- l_bc : b ve c adalarının doğrudan bağlantılı olup olmadığına dair ikili gösterge değişkeni (diğer bir deyişle, yalnızca b'den c'ye köprü karelerinde yürüyebilirsiniz).
Köprü inşa maliyetleri c_ij için , en aza indirilecek amaç değeri sum_ij c_ij * x_ij
. Modele aşağıdaki kısıtlamaları eklememiz gerekiyor:
- Y_ijbcn değişkenlerinin geçerli olduğundan emin olmamız gerekir . Her zaman bir su meydanına ancak oraya bir köprü inşa edersek ulaşabiliriz, yani
y_ijbcn <= x_ij
her su konumu için (i, j). Ayrıca, y_ijbc1
(i, j) ada b'yi sınırlamıyorsa, 0'a eşit olmalıdır. Son olarak, n> 1 için, y_ijbcn
yalnızca adım n-1'de bir komşu su konumu kullanılmışsa kullanılabilir. Tanımlama N(i, j)
komşu su kareler (i, j) için, bu eşdeğerdir y_ijbcn <= sum_{(l, m) in N(i, j)} y_lmbc(n-1)
.
- L_bc değişkenlerinin yalnızca b ve c bağlantılıysa ayarlandığından emin olmalıyız .
I(c)
Ada c'yi çevreleyen yerler olarak tanımlarsak , bu yapılabilir l_bc <= sum_{(i, j) in I(c), n} y_ijbcn
.
- Tüm adaların doğrudan veya dolaylı olarak bağlantılı olmasını sağlamalıyız. Bu, aşağıdaki şekilde gerçekleştirilebilir: Adaların boş olmayan her uygun alt kümesi için, S'deki en az bir adanın S'nin tamamlayıcısı içinde S 'olarak adlandıracağımız en az bir adaya bağlı olmasını gerektirir. Kısıtlamalarda, bunu boş olmayan her S kümesi için <= K / 2 boyutunda bir sınırlama ekleyerek uygulayabiliriz (burada K, ada sayısıdır)
sum_{b in S} sum_{c in S'} l_bc >= 1
.
K adaları, W su kareleri ve belirtilen maksimum yol uzunluğu N ile ilgili bir sorun örneği için bu, O(K^2WN)
değişkenler ve O(K^2WN + 2^K)
kısıtlamalar içeren bir karma tamsayı programlama modelidir . Açıkçası bu, problem boyutu büyüdükçe çözülemez hale gelecektir, ancak ilgilendiğiniz boyutlar için çözülebilir olabilir. Ölçeklenebilirlik hakkında bir fikir edinmek için, onu pulp paketini kullanarak python'da gerçekleştireceğim. Öncelikle sorunun altında 3 ada bulunan daha küçük 7 x 9 haritayla başlayalım:
import itertools
import pulp
water = {(0, 2): 2.0, (0, 3): 1.0, (0, 4): 1.0, (0, 5): 1.0, (0, 6): 2.0,
(1, 0): 2.0, (1, 1): 9.0, (1, 2): 1.0, (1, 3): 9.0, (1, 4): 9.0,
(1, 5): 9.0, (1, 6): 1.0, (1, 7): 9.0, (1, 8): 2.0,
(2, 0): 1.0, (2, 1): 9.0, (2, 2): 9.0, (2, 3): 1.0, (2, 4): 9.0,
(2, 5): 1.0, (2, 6): 9.0, (2, 7): 9.0, (2, 8): 1.0,
(3, 0): 9.0, (3, 1): 1.0, (3, 2): 9.0, (3, 3): 9.0, (3, 4): 5.0,
(3, 5): 9.0, (3, 6): 9.0, (3, 7): 1.0, (3, 8): 9.0,
(4, 0): 9.0, (4, 1): 9.0, (4, 2): 1.0, (4, 3): 9.0, (4, 4): 1.0,
(4, 5): 9.0, (4, 6): 1.0, (4, 7): 9.0, (4, 8): 9.0,
(5, 0): 9.0, (5, 1): 9.0, (5, 2): 9.0, (5, 3): 2.0, (5, 4): 1.0,
(5, 5): 2.0, (5, 6): 9.0, (5, 7): 9.0, (5, 8): 9.0,
(6, 0): 9.0, (6, 1): 9.0, (6, 2): 9.0, (6, 6): 9.0, (6, 7): 9.0,
(6, 8): 9.0}
islands = {0: [(0, 0), (0, 1)], 1: [(0, 7), (0, 8)], 2: [(6, 3), (6, 4), (6, 5)]}
N = 6
# Island borders
iborders = {}
for k in islands:
iborders[k] = {}
for i, j in islands[k]:
for dx in [-1, 0, 1]:
for dy in [-1, 0, 1]:
if (i+dx, j+dy) in water:
iborders[k][(i+dx, j+dy)] = True
# Create models with specified variables
x = pulp.LpVariable.dicts("x", water.keys(), lowBound=0, upBound=1, cat=pulp.LpInteger)
pairs = [(b, c) for b in islands for c in islands if b < c]
yvals = []
for i, j in water:
for b, c in pairs:
for n in range(N):
yvals.append((i, j, b, c, n))
y = pulp.LpVariable.dicts("y", yvals, lowBound=0, upBound=1)
l = pulp.LpVariable.dicts("l", pairs, lowBound=0, upBound=1)
mod = pulp.LpProblem("Islands", pulp.LpMinimize)
# Objective
mod += sum([water[k] * x[k] for k in water])
# Valid y
for k in yvals:
i, j, b, c, n = k
mod += y[k] <= x[(i, j)]
if n == 0 and not (i, j) in iborders[b]:
mod += y[k] == 0
elif n > 0:
mod += y[k] <= sum([y[(i+dx, j+dy, b, c, n-1)] for dx in [-1, 0, 1] for dy in [-1, 0, 1] if (i+dx, j+dy) in water])
# Valid l
for b, c in pairs:
mod += l[(b, c)] <= sum([y[(i, j, B, C, n)] for i, j, B, C, n in yvals if (i, j) in iborders[c] and B==b and C==c])
# All islands connected (directly or indirectly)
ikeys = islands.keys()
for size in range(1, len(ikeys)/2+1):
for S in itertools.combinations(ikeys, size):
thisSubset = {m: True for m in S}
Sprime = [m for m in ikeys if not m in thisSubset]
mod += sum([l[(min(b, c), max(b, c))] for b in S for c in Sprime]) >= 1
# Solve and output
mod.solve()
for row in range(min([m[0] for m in water]), max([m[0] for m in water])+1):
for col in range(min([m[1] for m in water]), max([m[1] for m in water])+1):
if (row, col) in water:
if x[(row, col)].value() > 0.999:
print "B",
else:
print "-",
else:
print "I",
print ""
Bu, kağıt hamuru paketinden (CBC çözücü) varsayılan çözücüyü kullanarak çalıştırmak 1,4 saniye sürer ve doğru çözümü verir:
I I - - - - - I I
- - B - - - B - -
- - - B - B - - -
- - - - B - - - -
- - - - B - - - -
- - - - B - - - -
- - - I I I - - -
Daha sonra, sorunun üst kısmındaki 7 adalı 13 x 14 ızgara olan sorunun tamamını düşünün:
water = {(i, j): 1.0 for i in range(13) for j in range(14)}
islands = {0: [(0, 0), (0, 1), (1, 0), (1, 1), (2, 0), (2, 1)],
1: [(9, 0), (9, 1), (10, 0), (10, 1), (10, 2), (11, 0), (11, 1),
(11, 2), (12, 0)],
2: [(0, 7), (0, 8), (1, 7), (1, 8), (2, 7)],
3: [(7, 7), (8, 6), (8, 7), (8, 8), (9, 7)],
4: [(0, 11), (0, 12), (0, 13), (1, 12)],
5: [(4, 10), (4, 11), (5, 10), (5, 11)],
6: [(11, 8), (11, 9), (11, 13), (12, 8), (12, 9), (12, 10), (12, 11),
(12, 12), (12, 13)]}
for k in islands:
for i, j in islands[k]:
del water[(i, j)]
for i, j in [(10, 7), (10, 8), (10, 9), (10, 10), (10, 11), (10, 12),
(11, 7), (12, 7)]:
water[(i, j)] = 20.0
N = 7
MIP çözücüler genellikle iyi çözümleri nispeten hızlı bir şekilde elde ederler ve ardından çözümün optimalliğini kanıtlamaya çalışmak için çok fazla zaman harcarlar. Yukarıdaki ile aynı çözücü kodunu kullanarak, program 30 dakika içinde tamamlanmaz. Ancak, yaklaşık bir çözüm elde etmek için çözücüye bir zaman aşımı süresi sağlayabilirsiniz:
mod.solve(pulp.solvers.PULP_CBC_CMD(maxSeconds=120))
Bu, hedef değeri 17 olan bir çözüm sağlar:
I I - - - - - I I - - I I I
I I - - - - - I I - - - I -
I I - - - - - I - B - B - -
- - B - - - B - - - B - - -
- - - B - B - - - - I I - -
- - - - B - - - - - I I - -
- - - - - B - - - - - B - -
- - - - - B - I - - - - B -
- - - - B - I I I - - B - -
I I - B - - - I - - - - B -
I I I - - - - - - - - - - B
I I I - - - - - I I - - - I
I - - - - - - - I I I I I I
Elde ettiğiniz çözümlerin kalitesini artırmak için ticari bir MIP çözücü kullanabilirsiniz (akademik bir kurumdaysanız bu ücretsizdir ve aksi takdirde büyük olasılıkla özgür değilsiniz). Örneğin, yine 2 dakikalık bir zaman sınırı ile Gurobi 6.0.4'ün performansı burada (çözüm günlüğünden çözücünün mevcut en iyi çözümü 7 saniye içinde bulduğunu okuyoruz):
mod.solve(pulp.solvers.GUROBI(timeLimit=120))
Bu aslında, OP'nin elle bulabileceğinden daha iyi olan 16 objektif değerinde bir çözüm bulur!
I I - - - - - I I - - I I I
I I - - - - - I I - - - I -
I I - - - - - I - B - B - -
- - B - - - - - - - B - - -
- - - B - - - - - - I I - -
- - - - B - - - - - I I - -
- - - - - B - - B B - - - -
- - - - - B - I - - B - - -
- - - - B - I I I - - B - -
I I - B - - - I - - - - B -
I I I - - - - - - - - - - B
I I I - - - - - I I - - - I
I - - - - - - - I I I I I I