Để tiếp cận vấn đề này, tôi sẽ sử dụng một khung lập trình số nguyên và xác định ba bộ biến quyết định:
- x_ij : Một biến chỉ báo nhị phân về việc chúng ta có xây dựng một cây cầu ở vị trí nước hay không (i, j).
- y_ijbcn : Một chỉ số nhị phân cho biết vị trí nước (i, j) có phải là vị trí thứ n nối đảo b với đảo c hay không.
- l_bc : Một biến chỉ báo nhị phân cho biết các đảo b và c có được liên kết trực tiếp hay không (bạn chỉ có thể đi bộ trên các ô cầu từ b đến c).
Đối với chi phí xây dựng cầu c_ij , giá trị mục tiêu cần giảm thiểu là sum_ij c_ij * x_ij
. Chúng ta cần thêm các ràng buộc sau vào mô hình:
- Chúng ta cần đảm bảo các biến y_ijbcn là hợp lệ. Chúng ta luôn chỉ có thể đến được một quảng trường nước nếu chúng ta xây một cây cầu ở đó, vì vậy
y_ijbcn <= x_ij
đối với mọi vị trí nước (i, j). Hơn nữa, y_ijbc1
phải bằng 0 nếu (i, j) không biên giới với đảo b. Cuối cùng, với n> 1,y_ijbcn
chỉ có thể được sử dụng nếu vị trí nước lân cận đã được sử dụng trong bước n-1. Định nghĩa N(i, j)
là các bình phương nước lân cận (i, j), điều này tương đương với y_ijbcn <= sum_{(l, m) in N(i, j)} y_lmbc(n-1)
.
- Chúng tôi cần đảm bảo biến l_bc chỉ được đặt nếu b và c được liên kết. Nếu chúng tôi xác định
I(c)
là các vị trí giáp với đảo c, điều này có thể được thực hiện với l_bc <= sum_{(i, j) in I(c), n} y_ijbcn
.
- Chúng ta cần đảm bảo rằng tất cả các đảo đều được liên kết, trực tiếp hoặc gián tiếp. Điều này có thể được thực hiện theo cách sau: đối với mỗi tập hợp con S các đảo thích hợp không trống, yêu cầu rằng ít nhất một đảo trong S được liên kết với ít nhất một đảo trong phần bổ sung của S, mà chúng ta sẽ gọi là S '. Trong các ràng buộc, chúng ta có thể thực hiện điều này bằng cách thêm một ràng buộc cho mọi tập không rỗng S có kích thước <= K / 2 (với K là số đảo) ,
sum_{b in S} sum_{c in S'} l_bc >= 1
.
Đối với một ví dụ bài toán với K đảo, W hình vuông nước và độ dài đường dẫn tối đa được chỉ định N, đây là một mô hình lập trình số nguyên hỗn hợp với O(K^2WN)
các biến và O(K^2WN + 2^K)
ràng buộc. Rõ ràng là điều này sẽ trở nên khó chữa khi kích thước vấn đề trở nên lớn, nhưng nó có thể giải quyết được đối với các kích thước bạn quan tâm. Để hiểu về khả năng mở rộng, tôi sẽ triển khai nó trong python bằng cách sử dụng gói bột giấy. Trước tiên, hãy bắt đầu với bản đồ 7 x 9 nhỏ hơn với 3 hòn đảo ở cuối câu hỏi:
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 ""
Quá trình này mất 1,4 giây để chạy bằng bộ giải mặc định từ gói bột giấy (bộ giải CBC) và xuất ra giải pháp chính xác:
I I - - - - - I I
- - B - - - B - -
- - - B - B - - -
- - - - B - - - -
- - - - B - - - -
- - - - B - - - -
- - - I I I - - -
Tiếp theo, hãy xem xét vấn đề đầy đủ ở đầu câu hỏi, đó là lưới 13 x 14 với 7 hòn đảo:
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
Những người giải MIP thường có được các giải pháp tốt tương đối nhanh chóng và sau đó dành một khoảng thời gian rất lớn để cố gắng chứng minh tính tối ưu của giải pháp. Sử dụng mã bộ giải tương tự như trên, chương trình không hoàn thành trong vòng 30 phút. Tuy nhiên, bạn có thể cung cấp thời gian chờ cho bộ giải để có được giải pháp gần đúng:
mod.solve(pulp.solvers.PULP_CBC_CMD(maxSeconds=120))
Điều này mang lại một giải pháp với giá trị mục tiêu 17:
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
Để cải thiện chất lượng của các giải pháp bạn nhận được, bạn có thể sử dụng trình giải MIP thương mại (điều này miễn phí nếu bạn đang ở một cơ sở giáo dục và có thể không miễn phí nếu không). Ví dụ: đây là hiệu suất của Gurobi 6.0.4, một lần nữa với giới hạn thời gian 2 phút (mặc dù từ nhật ký giải pháp, chúng tôi đọc rằng trình giải đã tìm thấy giải pháp tốt nhất hiện tại trong vòng 7 giây):
mod.solve(pulp.solvers.GUROBI(timeLimit=120))
Điều này thực sự tìm thấy một giải pháp có giá trị mục tiêu 16, một giải pháp tốt hơn so với OP có thể tìm thấy bằng tay!
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