이 문제에 접근하기 위해 정수 프로그래밍 프레임 워크를 사용하고 세 가지 의사 결정 변수 세트를 정의합니다.
- x_ij : 물 위치 (i, j)에 다리를 건설하는지 여부를 나타내는 이진 표시기 변수입니다.
- y_ijbcn : 물 위치 (i, j)가 섬 b와 섬 c를 연결하는 n 번째 위치인지 여부를 나타내는 이진 표시기입니다.
- l_bc : 섬 b와 c가 직접 연결되어 있는지 여부에 대한 이진 표시기 변수 (즉, b에서 c까지의 다리 광장에서만 걸을 수 있음).
교량 건설 비용 c_ij의 경우 최소화 할 목표 값은 sum_ij c_ij * x_ij
입니다. 모델에 다음 제약 조건을 추가해야합니다.
- y_ijbcn 변수가 유효한지 확인해야 합니다. 우리는 그곳에 다리를 건설해야만 항상 워터 스퀘어에 도달 할 수 있습니다.
y_ijbcn <= x_ij
모든 물 위치 (i, j)에 대해 . 또한 y_ijbc1
(i, j)가 아일랜드 b와 접하지 않으면 0이어야합니다. 마지막으로 n> 1 인 y_ijbcn
경우 n-1 단계에서 인접한 물 위치가 사용 된 경우에만 사용할 수 있습니다. N(i, j)
(i, j)에 인접한 물의 제곱으로 정의 하면 y_ijbcn <= sum_{(l, m) in N(i, j)} y_lmbc(n-1)
.
- 우리는 l_bc 변수는 b와 c가 연결된 경우에만 설정 .
I(c)
섬 c와 접하는 위치로 정의 하면 l_bc <= sum_{(i, j) in I(c), n} y_ijbcn
.
- 모든 섬이 직간접 적으로 연결되도록해야합니다. 이는 다음과 같은 방법으로 수행 할 수 있습니다. 비어 있지 않은 모든 적절한 섬의 하위 집합 S에 대해 S에있는 하나 이상의 섬이 S의 보완 물에있는 하나 이상의 섬에 연결되어야합니다.이를 S '라고합니다. 제약 조건에서 우리는 크기가 <= K / 2 인 비어 있지 않은 모든 세트 S (여기서 K는 섬의 수)에 대한 제약 조건을 추가하여이를 구현할 수 있습니다
sum_{b in S} sum_{c in S'} l_bc >= 1
.
K 아일랜드, W 워터 스퀘어 및 지정된 최대 경로 길이 N이있는 문제 인스턴스의 경우 이것은 O(K^2WN)
변수와O(K^2WN + 2^K)
제약 조건 . 분명히 이것은 문제 크기가 커짐에 따라 다루기 어렵지만 관심있는 크기에 대해서는 해결할 수 있습니다. 확장 성을 이해하기 위해 pulp 패키지를 사용하여 파이썬으로 구현할 것입니다. 먼저 질문 하단에 3 개의 섬이있는 작은 7 x 9지도부터 시작하겠습니다.
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 ""
펄프 패키지의 기본 솔버 (CBC 솔버)를 사용하여 실행하는 데 1.4 초가 걸리며 올바른 솔루션을 출력합니다.
I I - - - - - I I
- - B - - - B - -
- - - B - B - - -
- - - - B - - - -
- - - - B - - - -
- - - - B - - - -
- - - I I I - - -
다음으로, 7 개의 섬이있는 13 x 14 그리드 인 질문 상단의 전체 문제를 고려하십시오.
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 솔버는 종종 좋은 솔루션을 비교적 빠르게 얻은 다음 솔루션의 최적 성을 증명하는 데 많은 시간을 소비합니다. 위와 동일한 솔버 코드를 사용하면 프로그램이 30 분 이내에 완료되지 않습니다. 그러나 대략적인 솔루션을 얻기 위해 솔버에 시간 제한을 제공 할 수 있습니다.
mod.solve(pulp.solvers.PULP_CBC_CMD(maxSeconds=120))
이렇게하면 목표 값이 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
획득 한 솔루션의 품질을 개선하기 위해 상용 MIP 솔버를 사용할 수 있습니다 (교육 기관에있는 경우 무료이며 그렇지 않으면 무료 일 가능성이 없음). 예를 들어, Gurobi 6.0.4의 성능은 다시 2 분 시간 제한이 있습니다 (솔버가 7 초 이내에 현재 최상의 솔루션을 찾았다는 것을 솔루션 로그에서 읽었음에도 불구하고).
mod.solve(pulp.solvers.GUROBI(timeLimit=120))
이것은 실제로 OP가 손으로 찾을 수있는 것보다 나은 객관적인 값 16의 솔루션을 찾습니다!
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