Có nhiều cách để tiếp cận vấn đề này. Định dạng raster của dữ liệu cho thấy cách tiếp cận dựa trên raster; khi xem xét các cách tiếp cận đó, một công thức của vấn đề như một chương trình tuyến tính số nguyên nhị phân có vẻ đầy triển vọng, bởi vì nó rất giống với nhiều phân tích lựa chọn trang web của GIS và có thể dễ dàng thích nghi với chúng.
Trong công thức này, chúng tôi liệt kê tất cả các vị trí và hướng có thể có của đa giác điền, mà tôi sẽ gọi là "gạch". Liên kết với mỗi gạch là một thước đo "lòng tốt" của nó. Mục tiêu là tìm ra một bộ sưu tập gạch không chồng chéo mà tổng độ tốt của nó càng lớn càng tốt. Ở đây, chúng ta có thể lấy sự tốt đẹp của mỗi viên gạch để trở thành khu vực mà nó bao phủ. .
Các ràng buộc về vấn đề này chỉ đơn giản là không có hai ô trong một giải pháp có thể trùng nhau.
Điều này có thể được đóng khung một chút trừu tượng hơn, theo một cách có lợi cho tính toán hiệu quả, bằng cách liệt kê các tế bào trong đa giác được điền (các "khu vực") 1, 2, ..., M . Bất kỳ vị trí xếp gạch nào cũng có thể được mã hóa bằng một vectơ chỉ thị các số 0 và số 0, để cho các vị trí tương ứng với các ô được bao phủ bởi các ô và số không ở nơi khác. Trong bảng mã này, tất cả thông tin cần thiết về một tập hợp các ô có thể được tìm thấy bằng cách tính tổng các vectơ chỉ báo của chúng (thành phần theo thành phần, như thường lệ): tổng sẽ không chính xác trong đó ít nhất một ô bao phủ một ô và tổng sẽ lớn hơn hơn một nơi hai hoặc nhiều gạch chồng lên nhau. (Tổng số có hiệu quả đếm số lượng chồng lấp.)
Một trừu tượng ít hơn: tập hợp vị trí ngói có thể tự nó có thể được liệt kê, nói 1, 2, ..., N . Việc lựa chọn bất kỳ tập hợp các vị trí ô nào cũng tương ứng với một vectơ chỉ báo nơi các vị trí chỉ định các ô được đặt.
Đây là một minh họa nhỏ để sửa chữa các ý tưởng . Nó được đi kèm với Mathematica mã được sử dụng để thực hiện các phép tính, vì vậy mà những khó khăn lập trình (hoặc thiếu đó) có thể hiển nhiên.
Đầu tiên, chúng tôi mô tả một khu vực được lát gạch:
region = {{0, 0, 1, 1}, {1, 1, 1, 1}, {1, 1, 1, 1}, {1, 1, 1, 1}};
Nếu chúng ta đánh số các ô của nó từ trái sang phải, bắt đầu từ trên cùng, vectơ chỉ báo cho vùng có 16 mục:
Flatten[region]
{0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}
Hãy sử dụng ô sau đây, cùng với tất cả các phép quay theo bội số 90 độ:
tileSet = {{{1, 1}, {1, 0}}};
Mã để tạo các phép quay (và phản xạ):
apply[s_List, alpha] := Reverse /@ s;
apply[s_List, beta] := Transpose[s];
apply[s_List, g_List] := Fold[apply, s, g];
group = FoldList[Append, {}, Riffle[ConstantArray[alpha, 4], beta]];
tiles = Union[Flatten[Outer[apply[#1, #2] &, tileSet, group, 1], 1]];
(Tính toán hơi đục này được giải thích trong bài trả lời tại /math//a/159159 , trong đó cho thấy nó chỉ đơn giản là tạo ra tất cả các phép quay tốt và phản xạ của một gạch và sau đó loại bỏ bất kỳ kết quả trùng lặp.)
Giả sử chúng ta đã đặt gạch như hiển thị ở đây:
Các ô 3, 6 và 7 được bao phủ trong vị trí này. Điều đó được chỉ định bởi vectơ chỉ báo
{0, 0, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0}
Nếu chúng ta dịch chuyển một cột này sang bên phải, vectơ chỉ báo đó sẽ thay thế
{0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0}
Sự kết hợp của việc cố gắng đặt gạch tại cả hai vị trí này đồng thời được xác định bởi tổng của các chỉ số này,
{0, 0, 1, 1, 0, 1, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0}
Số 2 ở vị trí thứ bảy cho thấy các phần trùng nhau này trong một ô (hàng thứ hai trở xuống, cột thứ ba từ bên trái). Vì chúng tôi không muốn trùng lặp, chúng tôi sẽ yêu cầu tổng các vectơ trong bất kỳ giải pháp hợp lệ nào phải không có mục nào vượt quá 1.
Nó chỉ ra rằng đối với vấn đề này, 29 kết hợp định hướng và vị trí có thể cho gạch. (Điều này đã được tìm thấy với một chút mã hóa đơn giản liên quan đến tìm kiếm toàn diện.) Chúng ta có thể mô tả tất cả 29 khả năng bằng cách vẽ các chỉ số của chúng dưới dạng vectơ cột . (Sử dụng các cột thay vì các hàng là thông thường.) Đây là hình ảnh của mảng kết quả, sẽ có 16 hàng (một cho mỗi ô có thể có trong hình chữ nhật) và 29 cột:
makeAllTiles[tile_, {n_Integer, m_Integer}] :=
With[{ m0 = Length[tile], n0 = Length[First[tile]]},
Flatten[
Table[ArrayPad[tile, {{i, m - m0 - i}, {j, n - n0 - j}}], {i, 0, m - m0}, {j, 0, n - n0}], 1]];
allTiles = Flatten[ParallelMap[makeAllTiles[#, ImageDimensions[regionImage]] & , tiles], 1];
allTiles = Parallelize[
Select[allTiles, (regionVector . Flatten[#]) >= (Plus @@ (Flatten[#])) &]];
options = Transpose[Flatten /@ allTiles];
(Hai vectơ chỉ báo trước xuất hiện dưới dạng hai cột đầu tiên bên trái.) Trình đọc mắt sắc nét có thể nhận thấy một số cơ hội để xử lý song song: các tính toán này có thể mất vài giây.
Tất cả những điều đã nói ở trên có thể được trình bày lại một cách gọn gàng bằng cách sử dụng ký hiệu ma trận:
F là mảng tùy chọn này, với M hàng và N cột.
X là chỉ số của một tập hợp vị trí gạch, chiều dài N .
b là một N -vector của những người.
R là chỉ số cho khu vực; nó là một M -vector.
Tổng "độ tốt" liên quan đến bất kỳ giải pháp X có thể nào , bằng RFX , vì FX là chỉ số của các ô được bao phủ bởi X và sản phẩm có R tính tổng các giá trị này. (Chúng tôi có thể cân R nếu chúng tôi muốn các giải pháp ủng hộ hoặc tránh các khu vực nhất định trong khu vực.) Điều này sẽ được tối đa hóa. Bởi vì chúng ta có thể viết nó dưới dạng ( RF ). X , nó là một hàm tuyến tính của X : điều này rất quan trọng. (Trong đoạn mã dưới đây, biến c
chứa RF .)
Những hạn chế là có
Tất cả các yếu tố của X phải không âm;
Tất cả các phần tử của X phải nhỏ hơn 1 (là mục tương ứng trong b );
Tất cả các yếu tố của X phải là tích phân.
Các ràng buộc (1) và (2) biến điều này thành một chương trình tuyến tính , trong khi yêu cầu thứ ba biến nó thành một chương trình tuyến tính nguyên .
Tồn tại nhiều gói để giải các chương trình tuyến tính nguyên được thể hiện dưới dạng chính xác này. Chúng có khả năng xử lý các giá trị của M và N thành hàng chục hoặc thậm chí hàng trăm ngàn. Điều đó có lẽ đủ tốt cho một số ứng dụng trong thế giới thực.
Như minh họa đầu tiên của chúng tôi, tôi đã tính toán một giải pháp cho ví dụ trước bằng cách sử dụng lệnh của Mathicala 8 LinearProgramming
. (Điều này sẽ giảm thiểu chức năng mục tiêu tuyến tính. Tối thiểu hóa dễ dàng chuyển sang tối đa hóa bằng cách phủ định chức năng mục tiêu.) Nó trả về một giải pháp (dưới dạng danh sách các ô và vị trí của chúng) trong 0,011 giây:
b = ConstantArray[-1, Length[options]];
c = -Flatten[region].options;
lu = ConstantArray[{0, 1}, Length[First[options]]];
x = LinearProgramming[c, -options, b, lu, Integers, Tolerance -> 0.05];
If[! ListQ[x] || Max[options.x] > 1, x = {}];
solution = allTiles[[Select[x Range[Length[x]], # > 0 &]]];
Các tế bào màu xám không ở trong khu vực; các tế bào trắng không được bao phủ bởi giải pháp này.
Bạn có thể làm việc (bằng tay) nhiều cách khác cũng tốt như cái này - nhưng bạn không thể tìm thấy cái nào tốt hơn. Đó là một hạn chế tiềm năng của phương pháp này: nó mang đến cho bạn một giải pháp tốt nhất, ngay cả khi có nhiều hơn một. (Có một số cách giải quyết: nếu chúng tôi sắp xếp lại các cột của X , vấn đề vẫn không thay đổi, nhưng phần mềm thường chọn một giải pháp khác do kết quả. Tuy nhiên, hành vi này là không thể đoán trước.)
Như một minh họa thứ hai , để thực tế hơn, hãy xem xét khu vực trong câu hỏi. Bằng cách nhập hình ảnh và lấy mẫu lại, tôi biểu thị nó bằng lưới 69 x 81:
Vùng bao gồm 2156 ô của lưới này.
Để làm cho mọi thứ thú vị và để minh họa tính tổng quát của thiết lập lập trình tuyến tính, chúng ta hãy cố gắng bao phủ càng nhiều vùng này càng tốt với hai loại hình chữ nhật:
Một là 17 x 9 (153 ô) và hai là 15 x 11 (165 ô). Chúng tôi có thể thích sử dụng cái thứ hai, vì nó lớn hơn, nhưng cái thứ nhất thì gầy hơn và có thể vừa với những nơi chật hơn. Hãy xem nào!
Chương trình hiện có N = 5589 vị trí xếp có thể. Nó khá lớn! Sau 6,3 giây tính toán, Mathicala đã đưa ra giải pháp mười ô này:
Do một số độ chùng ( .eg, chúng ta có thể dịch chuyển ô bên trái phía dưới lên đến bốn cột sang bên trái), rõ ràng có một số giải pháp khác khác một chút so với giải pháp này.