Thuật toán thả bom


212

Tôi có một n x mma trận bao gồm các số nguyên không âm. Ví dụ:

2 3 4 7 1
1 5 2 6 2
4 3 4 2 1
2 1 2 4 1
3 1 3 4 1
2 1 4 3 2
6 9 1 6 4

"Thả bom" giảm một số lượng ô mục tiêu và tất cả tám hàng xóm của nó, xuống mức tối thiểu bằng không.

x x x 
x X x
x x x

Thuật toán xác định số lượng bom tối thiểu cần thiết để giảm tất cả các ô xuống 0 là gì?

Tùy chọn B (Do tôi không phải là người đọc cẩn thận)

Trên thực tế, phiên bản đầu tiên của vấn đề không phải là phiên bản tôi đang tìm kiếm câu trả lời. Tôi đã không đọc kỹ toàn bộ nhiệm vụ, có những ràng buộc bổ sung, hãy để chúng tôi nói:

Điều gì về vấn đề đơn giản, khi chuỗi liên tiếp phải không tăng:

8 7 6 6 5 là chuỗi đầu vào có thể

7 8 5 5 2 là không thể vì 7 -> 8 tăng theo trình tự.

Có lẽ việc tìm câu trả lời cho trường hợp "dễ dàng hơn" sẽ giúp tìm ra giải pháp cho câu hỏi khó hơn.

Tái bút: Tôi tin rằng khi chúng tôi có một vài tình huống giống nhau đòi hỏi những quả bom tối thiểu để xóa đường trên, chúng tôi chọn một quả bom sử dụng hầu hết các quả bom ở "bên trái" của hàng. Vẫn còn bằng chứng nào có thể đúng?


4
Chà tôi chỉ thấy rằng một số trường có thể bị bỏ qua như trong ví dụ 2 3 1 5 Thả nó vào 2,3,1 là vô nghĩa, bởi vì thả chúng vào chúng gây ra một số thiệt hại tập hợp con mà chúng ta có thể gây ra bằng cách thả vào 5. Nhưng không thể tìm cách làm cho nó hoạt động trên toàn cầu (nếu đó là cách chính xác). Xóa 2 yêu cầu sử dụng 2 quả bom rơi vào bất kỳ người hàng xóm nào và 5 quả bom có ​​chứa các bộ sát thương khác. Nhưng sau đó tôi không biết phải làm gì sau này khi bạn viết lại (sau khi giảm), sau đó bạn có hai lựa chọn (không có một thiệt hại nào xảy ra).
abc

23
Đây có phải là NP-hard? Nó có vẻ là một biến thể của Vấn đề Bảo hiểm Tối đa .
Bí ẩn

14
+1 vì đã cho tôi một điều thú vị để suy nghĩ
Nick Mitchinson

3
@Kostek, vấn đề rất lớn! Xin vui lòng gửi liên kết.
Đại tá Panic

5
có lẽ bạn nên làm rõ, bạn đã nói câu hỏi là: what's the minimum amount of bombs required to clean the board?Điều này có nghĩa là không nhất thiết phải tìm một mô hình ném bom thực sự, mà chỉ là số lượng bom tối thiểu?
Lie Ryan

Câu trả lời:


38

Có một cách để giảm vấn đề này thành một vấn đề phụ đơn giản.

Có 2 phần để giải thích, thuật toán và lý do thuật toán cung cấp một giải pháp tối ưu. Cái đầu tiên sẽ không có ý nghĩa nếu không có cái thứ hai, vì vậy tôi sẽ bắt đầu với lý do tại sao.

Nếu bạn nghĩ đến việc ném bom hình chữ nhật (giả sử hình chữ nhật lớn - chưa có trường hợp cạnh nào), bạn có thể thấy rằng cách duy nhất để giảm hình chữ nhật rỗng của hình vuông trên chu vi xuống 0 là ném bom theo chu vi hoặc đánh bom hình chữ nhật rỗng của hình vuông ngay bên trong chu vi. Tôi sẽ gọi chu vi lớp 1 và hình chữ nhật bên trong lớp 2.

Một cái nhìn sâu sắc quan trọng là không có điểm ném bom lớp 1, bởi vì "bán kính vụ nổ" bạn nhận được từ việc này luôn nằm trong bán kính vụ nổ của một hình vuông khác từ lớp 2. Bạn có thể dễ dàng thuyết phục bản thân về điều này.

Vì vậy, chúng ta có thể giảm vấn đề để tìm ra một cách tối ưu để đánh bom chu vi, sau đó chúng ta có thể lặp lại điều đó cho đến khi tất cả các ô vuông bằng 0.

Nhưng tất nhiên, điều đó sẽ không luôn luôn tìm ra giải pháp tối ưu nếu có thể ném bom chu vi theo kiểu kém tối ưu hơn, nhưng bằng cách sử dụng thêm bom X làm cho vấn đề giảm lớp bên trong đơn giản hơn bằng bom X. Vì vậy, nếu chúng ta gọi lớp permiter là một, nếu chúng ta đặt thêm một quả bom X ở đâu đó trong lớp 2 (ngay bên trong lớp 1), chúng ta có thể giảm nỗ lực ném bom lớp 2 sau hơn X không? Nói cách khác, chúng ta phải chứng minh rằng chúng ta có thể tham lam trong việc giảm chu vi bên ngoài.

Nhưng, chúng ta biết chúng ta có thể tham lam. Bởi vì không có quả bom nào ở lớp 2 có thể hiệu quả hơn trong việc giảm lớp 2 xuống 0 so với quả bom được đặt ở vị trí chiến lược ở lớp 3. Và vì lý do tương tự như trước đây - luôn có một quả bom chúng ta có thể đặt ở lớp 3 sẽ ảnh hưởng đến mọi ô vuông của lớp 2 mà một quả bom đặt trong lớp 2 có thể. Vì vậy, nó không bao giờ có thể làm hại chúng ta tham lam (theo nghĩa tham lam này).

Vì vậy, tất cả những gì chúng ta phải làm là tìm cách tối ưu để giảm permiter xuống 0 bằng cách ném bom lớp bên trong tiếp theo.

Chúng tôi không bao giờ bị tổn thương khi ném bom góc đầu tiên xuống 0, bởi vì chỉ có góc của lớp bên trong có thể chạm tới nó, vì vậy chúng tôi thực sự không có lựa chọn nào khác (và, bất kỳ quả bom nào trên vành đai có thể chạm tới góc đều có bán kính nổ trong bán kính vụ nổ từ góc của lớp bên trong).

Khi chúng ta đã làm như vậy, các ô vuông trên chu vi liền kề với góc 0 chỉ có thể đạt được bằng 2 ô vuông từ lớp bên trong:

0       A       B

C       X       Y

D       Z

Tại thời điểm này, chu vi thực sự là một vòng 1 chiều khép kín, bởi vì bất kỳ quả bom nào cũng sẽ làm giảm 3 ô vuông liền kề. Ngoại trừ một số điều kỳ lạ ở gần các góc - X có thể "đánh" A, B, C và D.

Bây giờ chúng ta không thể sử dụng bất kỳ thủ thuật bán kính vụ nổ nào - tình huống của mỗi hình vuông là đối xứng, ngoại trừ các góc kỳ lạ, và thậm chí không có bán kính vụ nổ là một tập hợp con khác. Lưu ý rằng nếu đây là một dòng (như Đại tá Panic thảo luận) thay vì một vòng khép kín thì giải pháp là không đáng kể. Điểm cuối phải giảm xuống 0, và nó không bao giờ gây hại cho bạn khi ném bom các điểm tiếp giáp với điểm cuối, một lần nữa bởi vì bán kính vụ nổ là một siêu đạn. Khi bạn đã tạo điểm cuối 0, bạn vẫn có điểm cuối mới, vì vậy hãy lặp lại (cho đến khi dòng là 0).

Vì vậy, nếu chúng ta có thể giảm tối ưu một hình vuông trong lớp xuống 0, chúng ta có một thuật toán (vì chúng ta đã cắt vòng lặp và bây giờ có một đường thẳng với các điểm cuối). Tôi tin rằng ném bom liền kề với hình vuông có giá trị thấp nhất (cung cấp cho bạn 2 tùy chọn) sao cho giá trị cao nhất trong 2 ô vuông có giá trị thấp nhất đó là tối thiểu có thể (bạn có thể phải chia nhỏ việc ném bom của mình để quản lý điều này) sẽ tối ưu nhưng tôi không (chưa?) có một bằng chứng.


+1 - Tôi sẽ viết một cái gì đó tương tự. Tôi nghĩ bạn đã có nó!
Rex Kerr

5
@beaker, vui lòng đọc kỹ vấn đề. Ném bom một hình vuông làm giảm tất cả tám hàng xóm của nó, vì vậy giả định của anh ta thực sự đúng.
darksky

20
But, we do know we can be greedy...- Tôi không mua cái này. Xem xét 1 1 2 1 1 2trên chu vi. Số lượng bom tối thiểu là 4, nhưng có ba giải pháp riêng biệt. Mỗi giải pháp có một tác động khác nhau trên lớp tiếp theo. Miễn là có nhiều giải pháp tối thiểu cho chu vi, bạn không thể cách ly hoàn toàn chu vi mà không xem xét các lớp bên trong. Tôi thực sự không nghĩ rằng vấn đề này có thể được giải quyết mà không cần quay lại.
dùng1354557

4
Tôi đã suy nghĩ về giải pháp này, nhưng nó trông có vẻ đơn giản. Đó là sự thật, rằng bạn có thể thả bom ở lớp 2 để làm sạch lớp 1, nhưng nếu có nhiều giải pháp, chúng có hiệu lực giải pháp cho các lớp cao hơn.
Luka Rahne

12
@psr: Điều này không hoạt động. Phương pháp ném bom tối ưu cho lớp ngoài có thể không tối ưu toàn cầu. Ví dụ : 0011100 0100010 0000000 0000000 1110111. Cách tối ưu để đánh bom lớp thứ nhất là ném bom vào giữa hàng thứ hai, lấy tổng cộng ba quả bom để tiêu diệt lớp bên ngoài. Nhưng sau đó, bạn cần hai quả bom để chăm sóc lớp tiếp theo. Tối ưu chỉ cần tổng cộng bốn quả bom: hai cho hai hàng đầu tiên và hai cho hàng cuối cùng.
nneonneo

26

Pólya nói "Nếu bạn không thể giải quyết vấn đề, thì có một vấn đề dễ dàng hơn bạn có thể giải quyết: tìm ra nó."

Vấn đề đơn giản rõ ràng hơn là vấn đề 1 chiều (khi lưới là một hàng đơn). Hãy bắt đầu với thuật toán đơn giản nhất - tham lam ném bom mục tiêu lớn nhất. Khi nào điều này đi sai?

Cho rằng 1 1 1, thuật toán tham lam không quan tâm đến ô nào nó ném bom đầu tiên. Tất nhiên, ô trung tâm là tốt hơn - nó đánh bại cả ba ô cùng một lúc. Điều này cho thấy một thuật toán mới A, "bom để giảm thiểu số tiền còn lại". Khi nào thuật toán này đi sai?

Cho trước 1 1 2 1 1, thuật toán A không phân biệt giữa ném bom các ô thứ 2, 3 hoặc 4. Nhưng ném bom vào ô thứ 2 để rời đi 0 0 1 1 1thì tốt hơn là ném bom vào ô thứ 3 để rời đi 1 0 1 0 1. Làm thế nào để khắc phục điều đó? Vấn đề với việc đánh bom ô thứ 3 là nó khiến chúng ta làm việc ở bên trái và làm việc ở bên phải phải được thực hiện riêng.

Làm thế nào về "bom để giảm thiểu số tiền còn lại, nhưng tối đa hóa tối thiểu ở bên trái (của nơi chúng tôi ném bom) cộng với tối thiểu ở bên phải". Gọi thuật toán này B. Khi nào thuật toán này bị lỗi?


Chỉnh sửa: Sau khi đọc các bình luận, tôi đồng ý một vấn đề thú vị hơn nhiều sẽ là vấn đề một chiều được thay đổi để các kết thúc tham gia. Rất thích nhìn thấy bất kỳ tiến bộ về điều đó.


40
Tôi không chắc tại sao câu trả lời này lại nhận được rất nhiều sự ủng hộ - trường hợp 1D gần như không đáng kể, chỉ đơn giản là luôn ném bom phần tử sang bên phải của phần tử dương đầu tiên. Điều này hoạt động vì luôn có chính xác một cách tối ưu để đánh bom bất kỳ phần tử nào chỉ chứa 0 'ở bên trái. Điều này có thể được mở rộng thành 2D để loại bỏ tối ưu các ô vuông góc, nhưng tôi không thấy một cách rõ ràng để mở rộng nó ra ngoài đó ...?
BlueRaja - Daniel Pflughoeft

3
@BlueRaja, tôi nêu lên vì nó minh họa rõ ràng rằng cách tiếp cận tham lam được thảo luận trong các câu trả lời khác là không đủ (ít nhất, nó cần phải được bổ sung với một tiêu chí bổ sung). Một số lựa chọn về mục tiêu, ngay cả khi chúng dẫn đến việc giảm tổng số bằng nhau, có thể khiến mọi thứ lan rộng hơn các mục tiêu khác. Tôi nghĩ rằng đây là một cái nhìn sâu sắc hữu ích cho vấn đề 2D.
Tim Goodman

3
Và nói chung "Nếu bạn bị kẹt trong trường hợp 2D, trước tiên hãy thử trường hợp 1D" là lời khuyên tốt.
Tim Goodman

21
@Tim: "'hãy thử trường hợp 1D trước' là lời khuyên tốt" Có, nó sẽ làm cho nó trở thành một nhận xét tuyệt vời; nhưng đó không phải là một câu trả lời ...
BlueRaja - Danny Pflughoeft

3
Tôi nghĩ rằng bạn có một điểm tốt mặc dù trường hợp 1D có thể hơi sai lệch ở đây bởi vì nó có một giải pháp đơn giản không dễ dàng mở rộng đến các chiều cao hơn. Tôi nghĩ rằng trường hợp 1D với các điều kiện biên định kỳ (trường hợp bao quanh) có thể tốt hơn.
Tim Goodman

12

Tôi đã chỉ dừng lại ở một giải pháp một phần kể từ khi hết thời gian, nhưng hy vọng ngay cả giải pháp một phần này cũng cung cấp một số hiểu biết về một phương pháp tiềm năng để giải quyết vấn đề này.

Khi gặp một vấn đề khó khăn, tôi muốn đưa ra những vấn đề đơn giản hơn để phát triển trực giác về không gian vấn đề. Ở đây, bước đầu tiên tôi thực hiện là giảm vấn đề 2-D này thành vấn đề 1-D. Hãy xem xét một dòng:

0 4 2 1 3 0 1

Bằng cách này hay cách khác, bạn biết rằng bạn sẽ cần phải ném bom tại hoặc xung quanh 4vị trí 4 lần để hạ nó xuống 0. Vì bên trái của vị trí là một số thấp hơn, không có lợi ích gì khi ném bom 0hoặc 4ném bom quá mức 2. Trên thực tế, tôi tin rằng (nhưng thiếu một bằng chứng nghiêm ngặt) rằng ném bom 2cho đến khi 4điểm rơi xuống 0 ít nhất là tốt như bất kỳ chiến lược nào khác để 4hạ nó xuống 0. Người ta có thể đi xuống từ trái sang phải trong chiến lược như thế này:

index = 1
while index < line_length
  while number_at_index(index - 1) > 0
    bomb(index)
  end
  index++
end
# take care of the end of the line
while number_at_index(index - 1) > 0
  bomb(index - 1)
end

Một vài đơn đặt hàng ném bom mẫu:

0 4[2]1 3 0 1
0 3[1]0 3 0 1
0 2[0]0 3 0 1
0 1[0]0 3 0 1
0 0 0 0 3[0]1
0 0 0 0 2[0]0
0 0 0 0 1[0]0
0 0 0 0 0 0 0

4[2]1 3 2 1 5
3[1]0 3 2 1 5
2[0]0 3 2 1 5
1[0]0 3 2 1 5
0 0 0 3[2]1 5
0 0 0 2[1]0 5
0 0 0 1[0]0 5
0 0 0 0 0 0[5]
0 0 0 0 0 0[4]
0 0 0 0 0 0[3]
0 0 0 0 0 0[2]
0 0 0 0 0 0[1]
0 0 0 0 0 0 0

Ý tưởng bắt đầu với một số cần phải đi theo cách này hay cách khác là một điều hấp dẫn bởi vì nó đột nhiên có thể đạt được để tìm một giải pháp mà một số người cho rằng ít nhất là tốt như tất cả các giải pháp khác.

Bước tiếp theo trong sự phức tạp trong đó việc tìm kiếm ít nhất là tốt vẫn khả thi là ở rìa của bảng. Rõ ràng với tôi rằng không bao giờ có bất kỳ lợi ích nghiêm ngặt nào để ném bom ra rìa ngoài; tốt hơn hết là bạn nên ném bom tại chỗ một và nhận ba không gian khác miễn phí. Với điều này, chúng ta có thể nói rằng ném bom vào vòng một bên trong cạnh ít nhất cũng tốt như ném bom vào rìa. Hơn nữa, chúng ta có thể kết hợp điều này với trực giác rằng ném bom bên phải vào rìa thực sự là cách duy nhất để giảm không gian cạnh xuống 0. Thậm chí, rất đơn giản để tìm ra chiến lược tối ưu (trong đó là ít nhất là tốt như bất kỳ chiến lược nào khác) để giảm số góc xuống 0. Chúng tôi kết hợp tất cả lại với nhau và có thể tiến gần hơn đến một giải pháp trong không gian 2 chiều.

Dựa vào sự quan sát về các mảnh góc, chúng ta có thể nói chắc chắn rằng chúng ta biết chiến lược tối ưu để đi từ bất kỳ bảng khởi đầu nào đến một bảng có số không ở tất cả các góc. Đây là một ví dụ về một bảng như vậy (tôi đã mượn các số từ hai bảng tuyến tính ở trên). Tôi đã gắn nhãn một số không gian khác nhau và tôi sẽ giải thích lý do.

0 4 2 1 3 0 1 0
4 x x x x x x 4
2 y y y y y y 2
1 y y y y y y 1
3 y y y y y y 3
2 y y y y y y 2
1 y y y y y y 1
5 y y y y y y 5
0 4 2 1 3 0 1 0

Người ta sẽ nhận thấy ở hàng trên cùng thực sự gần giống với ví dụ tuyến tính mà chúng ta đã thấy trước đó. Nhắc lại quan sát trước đây của chúng tôi rằng cách tối ưu để có được hàng trên cùng xuống 0 là ném bom hàng thứ hai ( xhàng). Không có cách nào để xóa hàng trên cùng bằng cách ném bom bất kỳ yhàng nào và không có thêm lợi ích nào khi ném bom hàng trên cùng vào việc ném bom không gian tương ứng trên xhàng.

Chúng tôi có thể áp dụng các chiến lược tuyến tính từ trên xuống (ném bom các không gian tương ứng trên xhàng), liên quan đến chính mình chỉ với dòng đầu tiên và không có gì khác. Nó sẽ đi một cái gì đó như thế này:

0 4 2 1 3 0 1 0
4 x[x]x x x x 4
2 y y y y y y 2
1 y y y y y y 1
3 y y y y y y 3
2 y y y y y y 2
1 y y y y y y 1
5 y y y y y y 5
0 4 2 1 3 0 1 0

0 3 1 0 3 0 1 0
4 x[x]x x x x 4
2 y y y y y y 2
1 y y y y y y 1
3 y y y y y y 3
2 y y y y y y 2
1 y y y y y y 1
5 y y y y y y 5
0 4 2 1 3 0 1 0

0 2 0 0 3 0 1 0
4 x[x]x x x x 4
2 y y y y y y 2
1 y y y y y y 1
3 y y y y y y 3
2 y y y y y y 2
1 y y y y y y 1
5 y y y y y y 5
0 4 2 1 3 0 1 0

0 1 0 0 3 0 1 0
4 x[x]x x x x 4
2 y y y y y y 2
1 y y y y y y 1
3 y y y y y y 3
2 y y y y y y 2
1 y y y y y y 1
5 y y y y y y 5
0 4 2 1 3 0 1 0

0 0 0 0 3 0 1 0
4 x x x x x x 4
2 y y y y y y 2
1 y y y y y y 1
3 y y y y y y 3
2 y y y y y y 2
1 y y y y y y 1
5 y y y y y y 5
0 4 2 1 3 0 1 0

Lỗ hổng trong cách tiếp cận này trở nên rất rõ ràng trong hai vụ đánh bom cuối cùng. Rõ ràng, cho rằng các trang web bom duy nhất làm giảm 4con số trong cột đầu tiên trong hàng thứ hai là đầu tiên xy. Hai vụ đánh bom cuối cùng rõ ràng không thua kém gì vụ đánh bom đầu tiên x, điều này sẽ thực hiện chính xác (liên quan đến vị trí đầu tiên ở hàng trên cùng, mà chúng tôi không có cách nào khác để giải tỏa). Vì chúng tôi đã chứng minh rằng chiến lược hiện tại của chúng tôi là tối ưu, rõ ràng cần phải sửa đổi chiến lược.

Tại thời điểm này, tôi có thể lùi một bước xuống phức tạp và chỉ tập trung vào một góc. Hãy xem xét điều này:

0 4 2 1
4 x y a
2 z . .
1 b . .

Rõ ràng là cách duy nhất để có được những không gian với 4xuống không là đánh bom một số sự kết hợp của x, yz. Với một số nhào lộn trong tâm trí của tôi, tôi khá chắc chắn rằng giải pháp tối ưu là ném bom xba lần và sau ađó b. Bây giờ vấn đề là tìm ra cách tôi đạt được giải pháp đó và nếu nó tiết lộ bất kỳ trực giác nào chúng ta có thể sử dụng để giải quyết vấn đề cục bộ này. Tôi nhận thấy rằng không có vụ đánh bom yzkhông gian. Cố gắng tìm một góc nơi ném bom những không gian đó có ý nghĩa mang lại một góc trông như thế này:

0 4 2 5 0
4 x y a .
2 z . . .
5 b . . .
0 . . . .

Đối với điều này, rõ ràng với tôi rằng giải pháp tối ưu là ném bom y5 lần và z5 lần. Hãy tiến thêm một bước nữa.

0 4 2 5 6 0 0
4 x y a . . .
2 z . . . . .
5 b . . . . .
6 . . . . . .
0 . . . . . .
0 . . . . . .

Ở đây, nó cảm thấy tương tự trực quan rằng giải pháp tối ưu là ném bom ab6 lần rồi x4 lần.

Bây giờ nó trở thành một trò chơi làm thế nào để biến những trực giác đó thành nguyên tắc mà chúng ta có thể xây dựng.

Hy vọng sẽ được tiếp tục!


10

Đối với câu hỏi cập nhật, một thuật toán tham lam đơn giản cho kết quả tối ưu.

Thả bom A [0,0] vào ô A [1,1], sau đó thả bom A [1,0] vào ô A [2,1] và tiếp tục quá trình này xuống dưới. Để làm sạch góc dưới bên trái, thả bom tối đa (A [N-1,0], A [N-2,0], A [N-3.0]) vào ô A [N-2.1]. Điều này sẽ làm sạch hoàn toàn 3 cột đầu tiên.

Với cách tiếp cận tương tự, làm sạch các cột 3,4,5, sau đó các cột 6,7,8, v.v.

Thật không may, điều này không giúp tìm ra giải pháp cho vấn đề ban đầu.


Vấn đề "Lớn hơn" (không có ràng buộc "không tăng") có thể được chứng minh là NP-hard. Dưới đây là bản phác thảo của một bằng chứng.

Giả sử chúng ta có một đồ thị phẳng có độ lên tới 3. Chúng ta hãy tìm nắp đỉnh tối thiểu cho đồ thị này. Theo bài viết trên Wikipedia, vấn đề này là NP-hard đối với các đồ thị phẳng có độ tới 3. Điều này có thể được chứng minh bằng cách giảm từ Planar 3SAT. Và độ cứng của Planar 3SAT - bằng cách giảm từ 3SAT. Cả hai bằng chứng này đều được trình bày trong các bài giảng gần đây trong "Thuật toán giới hạn dưới" của prof. Erik Demaine (bài 7 và 9).

Nếu chúng ta chia một số cạnh của biểu đồ gốc (biểu đồ bên trái trên sơ đồ), mỗi cạnh có số nút bổ sung chẵn, biểu đồ kết quả (biểu đồ bên phải trên sơ đồ) sẽ có cùng một đỉnh đỉnh tối thiểu cho các đỉnh ban đầu. Phép biến đổi như vậy cho phép căn chỉnh các đỉnh đồ thị với các vị trí tùy ý trên lưới.

nhập mô tả hình ảnh ở đây

Nếu chúng ta chỉ đặt các đỉnh đồ thị cho các hàng và cột chẵn (theo cách mà không có hai cạnh nào xảy ra với một đỉnh tạo thành một góc nhọn), hãy chèn "các đỉnh" bất cứ nơi nào có cạnh và chèn "số không" vào các vị trí lưới khác, chúng ta có thể sử dụng bất kỳ giải pháp nào cho bài toán ban đầu để tìm nắp đỉnh tối thiểu.


Đồ thị từ bên trái đến từ đâu? Xin lỗi, tôi không hiểu lời giải thích của bạn!
ryyst

1
@ryyst: đồ thị bên trái chỉ là một ví dụ về đồ thị phẳng. Nó được sử dụng để chứng minh làm thế nào để biến đổi bất kỳ đồ thị phẳng có độ nào lên đến 4 thành đồ thị được căn chỉnh theo lưới và sau đó đến ma trận n * m. Thuật toán "thả bom" được áp dụng cho ma trận này sẽ giải quyết vấn đề che đỉnh cho đồ thị được chuyển đổi này và do đó cho đồ thị "bên trái" đó.
Evgeny Kluev

Ah, tôi hiểu rồi, và tôi tin sự biến đổi của bạn là chính xác. Cảm ơn!
ryyst

@EvgenyKluev, tôi nghĩ bây giờ bạn cần chứng minh rằng đỉnh đỉnh vẫn là NP-hard cho "đồ thị phẳng có độ tới 4".
Shahbaz

@Shahbaz: Tôi sợ bằng chứng này sẽ quá dài. Vì vậy, tôi đã thêm liên kết đến bằng chứng.
Evgeny Kluev

9

Bạn có thể biểu diễn vấn đề này như vấn đề lập trình số nguyên . (đây chỉ là một trong những giải pháp khả thi để tiếp cận vấn đề này)

Có điểm:

a b c d
e f g h
i j k l
m n o p

người ta có thể viết 16 phương trình trong đó ví dụ cho điểm f

f <= ai + bi + ci + ei + fi + gi + ii + ji + ki   

tối thiểu hóa trên tổng của tất cả các chỉ mục và giải pháp số nguyên.

Giải pháp tất nhiên là tổng của các chỉ số này.

Điều này có thể được đơn giản hóa hơn nữa bằng cách đặt tất cả xi trên ranh giới 0, vì vậy cuối cùng bạn có phương trình 4 + 1 trong ví dụ này.

Vấn đề là không có algorhitm tầm thường để giải quyết các vấn đề như vậy. Tôi không phải là chuyên gia về vấn đề này, nhưng giải quyết vấn đề này vì lập trình tuyến tính là NP khó.


8
Tất cả các vấn đề trong NP có thể được coi là các vấn đề lập trình số nguyên, vì vậy điều này không hữu ích lắm, trừ khi chúng ta đã biết rằng vấn đề là NP-Complete
BlueRaja - Danny Pflughoeft

1
Tôi đồng ý. Cũng không cần thiết phải biết chính xác các động thái phải được thực hiện để biết giải pháp là gì.
Luka Rahne

1
Khi bạn đặt ranh giới thành 0, số lượng bất đẳng thức vẫn là 16.
darksky 16/03/13

9

Đây là một câu trả lời một phần, tôi đang cố gắng tìm một giới hạn dưới và giới hạn trên có thể là số lượng bom có ​​thể.

Trong 3x3 và bảng nhỏ hơn, giải pháp luôn luôn là ô được đánh số lớn nhất.

Trong các bảng lớn hơn 4 x 4, giới hạn dưới rõ ràng đầu tiên là tổng của các góc:

*2* 3  7 *1*
 1  5  6  2
 2  1  3  2
*6* 9  6 *4*

tuy nhiên, bạn sắp xếp quả bom, không thể xóa bảng 4 x 4 này với ít hơn 2 + 1 + 6 + 4 = 13 quả bom.

Nó đã được đề cập trong các câu trả lời khác rằng đặt quả bom vào góc thứ hai để loại bỏ góc không bao giờ tệ hơn việc đặt quả bom vào góc, vì vậy hãy đưa ra bảng:

*2* 3  4  7 *1*
 1  5  2  6  2
 4  3  4  2  1
 2  1  2  4  1
 3  1  3  4  1
 2  1  4  3  2
*6* 9  1  6 *4*

Chúng ta có thể loại bỏ các góc bằng cách đặt bom vào góc thứ hai để đưa ra một bảng mới:

 0  1  1  6  0
 0  3  0  5  1
 2  1  1  1  0
 2  1  2  4  1
 0  0  0  0  0
 0  0  0  0  0
 0  3  0  2  0

Càng xa càng tốt. Chúng tôi cần 13 quả bom để xóa các góc.

Bây giờ hãy quan sát số 6, 4, 3 và 2 được đánh dấu bên dưới:

 0  1  1 *6* 0
 0  3  0  5  1
 2  1  1  1  0
*2* 1  2 *4* 1
 0  0  0  0  0
 0  0  0  0  0
 0 *3* 0  2  0

Không có cách nào để ném bom bất kỳ hai trong số các tế bào đó bằng một quả bom, do đó, quả bom tối thiểu đã tăng thêm 6 + 4 + 3 + 2, vì vậy, thêm vào số lượng bom chúng tôi sử dụng để xóa các góc, chúng tôi nhận được mức tối thiểu số lượng bom cần thiết cho bản đồ này đã trở thành 28 quả bom. Không thể xóa bản đồ này với ít hơn 28 quả bom, đây là giới hạn dưới của bản đồ này.

Bạn có thể sử dụng thuật toán tham lam để thiết lập giới hạn trên. Các câu trả lời khác đã chỉ ra rằng một thuật toán tham lam tạo ra một giải pháp sử dụng 28 quả bom. Vì chúng tôi đã chứng minh trước đó rằng không có giải pháp tối ưu nào có thể có ít hơn 28 quả bom, do đó, 28 quả bom thực sự là một giải pháp tối ưu.

Khi tham lam và phương pháp tìm giới hạn tối thiểu mà tôi đã đề cập ở trên không hội tụ, tôi đoán bạn phải quay lại để kiểm tra tất cả các kết hợp.

Thuật toán tìm giới hạn dưới là như sau:

  1. Chọn một phần tử có số cao nhất, đặt tên là P.
  2. Đánh dấu tất cả các ô cách hai bước P và P là không thể mở được.
  3. Thêm P vào minimumsdanh sách.
  4. Lặp lại bước 1 cho đến khi tất cả các ô không thể mở được.
  5. Tổng hợp minimumsdanh sách để có được giới hạn dưới.

9

Đây sẽ là một cách tiếp cận tham lam:

  1. Tính ma trận "điểm" theo thứ tự n X m, trong đó điểm [i] [j] là tổng số điểm trừ trong ma trận nếu vị trí (i, j) bị ném bom. (Điểm tối đa của một điểm là 9 và điểm tối thiểu là 0)

  2. Di chuyển hàng khôn ngoan, tìm và chọn vị trí đầu tiên với số điểm cao nhất (giả sử (i, j)).

  3. Bom (i, j). Tăng số lượng bom.

  4. Nếu tất cả các phần tử của ma trận gốc không bằng 0, thì goto 1.

Tôi có nghi ngờ rằng đây là giải pháp tối ưu mặc dù.

Biên tập:

Cách tiếp cận tham lam mà tôi đã đăng ở trên, trong khi nó hoạt động, hầu hết có lẽ không cung cấp cho chúng tôi giải pháp tối ưu. Vì vậy, tôi nghĩ rằng nên thêm một số yếu tố của DP vào nó.

Tôi nghĩ rằng chúng ta có thể đồng ý rằng tại bất kỳ thời điểm nào, một trong những vị trí có "điểm" cao nhất (điểm [i] [j] = tổng số điểm trừ nếu (i, j) bị ném bom) phải được nhắm mục tiêu. Bắt đầu với giả định này, đây là cách tiếp cận mới:

NumOfBombs (M): (trả về số lần ném bom tối thiểu cần thiết)

  1. Cho ma trận M có thứ tự n X m. Nếu tất cả các phần tử của M bằng 0, thì trả về 0.

  2. Tính ma trận "điểm" M.

    Đặt k vị trí riêng biệt P1, P2, ... Pk (1 <= k <= n * m), là các vị trí trong M có điểm số cao nhất.

  3. trả về (1 + phút (NumOfBombs (M1), NumOfBombs (M2), ..., NumOfBombs (Mk)))

    trong đó M1, M2, ..., Mk là các ma trận kết quả nếu chúng ta ném bom các vị trí lần lượt là P1, P2, ..., Pk.

Ngoài ra, nếu chúng tôi muốn thứ tự của các vị trí ngoài nuke, chúng tôi sẽ phải theo dõi kết quả của "min".


3
Tôi tự hỏi nếu đặt điểm thành tổng của các giá trị hiện tại sẽ tạo ra kết quả tốt hơn. Điều đó về cơ bản sẽ làm phẳng mặt đất hiệu quả hơn.
Eugene

@Eugene: Điểm rất thú vị. Tôi không thể nghĩ ra lý do tại sao cách của bạn không tạo ra kết quả tốt hơn ...
SidR

@Eugene: Có lẽ tổng các giá trị hiện tại trong vùng lân cận có thể được sử dụng cho biện pháp "ưu tiên"? Nuke nút có điểm số cao nhất và ưu tiên cao nhất ..
SidR

Chỉ cần đọc câu trả lời này, tôi nghĩ nó giống với câu trả lời thứ hai tôi vừa đăng (có thể đánh vần thêm một chút trong câu trả lời của tôi). Tôi nghĩ sẽ là tối ưu nếu luôn có một khoảng trống với điểm tối đa, bởi vì bạn được đảm bảo rằng mọi vụ đánh bom đều có ảnh hưởng lớn nhất có thể. Thứ tự của các vụ đánh bom không thành vấn đề, do đó, việc thực hiện tốt nhất ở mỗi bước sẽ là tối ưu. Nhưng vì có thể có mối quan hệ "tốt nhất", nên có thể đối với một giải pháp tối ưu, bạn cần phải theo dõi lại và thử cả hai khi có sự ràng buộc.
Tim Goodman

1
@Eugene, có lẽ tôi không theo dõi bạn. Sự khác biệt giữa mức giảm lớn nhất và tổng nhỏ nhất của tất cả các giá trị còn lại là gì? Tổng các giá trị còn lại (sau khi ném bom) chỉ là tổng giá trị hiện tại trừ đi việc giảm từ ném bom vào không gian đó, vậy những thứ này có tương đương không?
Tim Goodman

8

Vấn đề mới của bạn , với các giá trị không tăng trên các hàng, khá dễ giải quyết.

Quan sát rằng cột bên trái chứa các số cao nhất. Do đó, bất kỳ giải pháp tối ưu nào trước tiên phải giảm cột này về không. Do đó, chúng ta có thể thực hiện một cuộc ném bom 1-D chạy qua cột này, giảm mọi yếu tố trong đó xuống không. Chúng tôi để bom rơi trên cột thứ hai để chúng gây sát thương tối đa. Có rất nhiều bài viết ở đây liên quan đến trường hợp 1D, tôi nghĩ vậy, vì vậy tôi cảm thấy an toàn trong việc bỏ qua trường hợp đó. (Nếu bạn muốn tôi mô tả nó, tôi có thể.). Do thuộc tính giảm, ba cột ngoài cùng bên trái sẽ giảm xuống không. Nhưng, chúng tôi chắc chắn sẽ sử dụng số lượng bom tối thiểu ở đây vì cột bên trái phải bằng không.

Bây giờ, một khi cột bên trái bằng 0, chúng ta chỉ cần cắt bỏ ba cột ngoài cùng bên trái bây giờ bằng 0 và lặp lại với ma trận giảm bây giờ. Điều này phải cung cấp cho chúng tôi một giải pháp tối ưu vì ở mỗi giai đoạn chúng tôi sử dụng số lượng bom tối thiểu có thể chứng minh được.


Tôi hiểu rồi. Tôi nghĩ về ý tưởng tương tự. : S Lần sau tôi sẽ đọc kỹ hơn. Nhưng nhờ có nhiều người có 'vấn đề' tốt đẹp để giải quyết '.
abc

4

Mathicala Integer Tuyến tính lập trình sử dụng nhánh và giới hạn

Như đã được đề cập, vấn đề này có thể được giải quyết bằng lập trình tuyến tính số nguyên (đó là NP-Hard ). Mathematica đã tích hợp sẵn ILP. "To solve an integer linear programming problem Mathematica first solves the equational constraints, reducing the problem to one containing inequality constraints only. Then it uses lattice reduction techniques to put the inequality system in a simpler form. Finally, it solves the simplified optimization problem using a branch-and-bound method."[Xem Hướng dẫn tối ưu hóa có ràng buộc trong Mathicala ..]

Tôi đã viết đoạn mã sau sử dụng các thư viện ILP của Mathicala. Nó nhanh đến mức đáng ngạc nhiên.

solveMatrixBombProblem[problem_, r_, c_] := 
 Module[{}, 
  bombEffect[x_, y_, m_, n_] := 
   Table[If[(i == x || i == x - 1 || i == x + 1) && (j == y || 
        j == y - 1 || j == y + 1), 1, 0], {i, 1, m}, {j, 1, n}];
  bombMatrix[m_, n_] := 
   Transpose[
    Table[Table[
      Part[bombEffect[(i - Mod[i, n])/n + 1, Mod[i, n] + 1, m, 
        n], (j - Mod[j, n])/n + 1, Mod[j, n] + 1], {j, 0, 
       m*n - 1}], {i, 0, m*n - 1}]];
  X := x /@ Range[c*r];
  sol = Minimize[{Total[X], 
     And @@ Thread[bombMatrix[r, c].X >= problem] && 
      And @@ Thread[X >= 0] && Total[X] <= 10^100 && 
      Element[X, Integers]}, X];
  Print["Minimum required bombs = ", sol[[1]]];
  Print["A possible solution = ", 
   MatrixForm[
    Table[x[c*i + j + 1] /. sol[[2]], {i, 0, r - 1}, {j, 0, 
      c - 1}]]];]

Đối với ví dụ được cung cấp trong bài toán:

solveMatrixBombProblem[{2, 3, 4, 7, 1, 1, 5, 2, 6, 2, 4, 3, 4, 2, 1, 2, 1, 2, 4, 1, 3, 1, 3, 4, 1, 2, 1, 4, 3, 2, 6, 9, 1, 6, 4}, 7, 5]

Đầu ra

nhập mô tả hình ảnh ở đây

Đối với bất cứ ai đọc điều này với một thuật toán tham lam

Hãy thử mã của bạn về vấn đề 10x10 sau:

5   20  7   1   9   8   19  16  11  3  
17  8   15  17  12  4   5   16  8   18  
4   19  12  11  9   7   4   15  14  6  
17  20  4   9   19  8   17  2   10  8  
3   9   10  13  8   9   12  12  6   18  
16  16  2   10  7   12  17  11  4   15  
11  1   15  1   5   11  3   12  8   3  
7   11  16  19  17  11  20  2   5   19  
5   18  2   17  7   14  19  11  1   6  
13  20  8   4   15  10  19  5   11  12

Ở đây nó được phân tách bằng dấu phẩy:

5, 20, 7, 1, 9, 8, 19, 16, 11, 3, 17, 8, 15, 17, 12, 4, 5, 16, 8, 18, 4, 19, 12, 11, 9, 7, 4, 15, 14, 6, 17, 20, 4, 9, 19, 8, 17, 2, 10, 8, 3, 9, 10, 13, 8, 9, 12, 12, 6, 18, 16, 16, 2, 10, 7, 12, 17, 11, 4, 15, 11, 1, 15, 1, 5, 11, 3, 12, 8, 3, 7, 11, 16, 19, 17, 11, 20, 2, 5, 19, 5, 18, 2, 17, 7, 14, 19, 11, 1, 6, 13, 20, 8, 4, 15, 10, 19, 5, 11, 12

Đối với vấn đề này, giải pháp của tôi chứa 208 quả bom. Đây là một giải pháp khả thi (tôi đã có thể giải quyết vấn đề này trong khoảng 12 giây).

nhập mô tả hình ảnh ở đây

Như một cách để kiểm tra kết quả mà Mathicala đang tạo ra, hãy xem liệu thuật toán tham lam của bạn có thể làm tốt hơn không.


Tôi đã có thể làm điều đó vào năm 219 với câu trả lời này: stackoverflow.com/questions/15300149/bomb-dropping-alacticm/ Kẻ
Anthony Queen

3

Không cần phải chuyển đổi vấn đề sang các vấn đề phụ tuyến tính.

Thay vào đó sử dụng một heuristic tham lam đơn giản, đó là đánh bom các góc , bắt đầu với cái lớn nhất.

Trong ví dụ đã cho có bốn góc, {2, 1, 6, 4}. Đối với mỗi góc, không có cách nào tốt hơn là ném bom theo đường chéo vào góc, vì vậy chúng ta biết rằng thực tế các vụ đánh bom 2 + 1 + 6 + 4 = 13 đầu tiên của chúng ta phải nằm trong các ô chéo này. Sau khi thực hiện vụ đánh bom, chúng ta chỉ còn lại một ma trận mới:

2 3 4 7 1      0 1 1 6 0      0 1 1 6 0     1 1 6 0     0 0 5     0 0 0 
1 5 2 6 2      0 3 0 5 1      0 3 0 5 1  => 1 0 4 0  => 0 0 3  => 0 0 0  
4 3 4 2 1      2 1 1 1 0      2 1 1 1 0     0 0 0 0     0 0 0     0 0 3  
2 1 2 4 1  =>  2 1 2 4 1  =>  2 1 2 4 1     0 0 3 0     0 0 3      
3 1 3 4 1      0 0 0 0 0      0 0 0 0 0 
2 1 4 3 2      0 0 0 0 0      0 0 0 0 0 
6 9 1 6 4      0 3 0 2 0      0 0 0 0 0 

Sau 13 vụ đánh bom đầu tiên, chúng tôi sử dụng phương pháp heuristic để loại bỏ 3 0 2 thông qua ba vụ đánh bom. Bây giờ, chúng ta có 2 góc mới, {2, 1} ở hàng thứ 4. Chúng tôi ném bom những người đó, 3 vụ đánh bom khác. Chúng tôi đã giảm ma trận xuống 4 x 4 bây giờ. Có một góc, phía trên bên trái. Chúng tôi đánh bom điều đó. Bây giờ chúng ta có 2 góc còn lại, {5, 3}. Vì 5 là góc lớn nhất mà chúng ta ném bom đầu tiên, 5 vụ đánh bom, sau đó cuối cùng đánh bom 3 ở góc khác. Tổng cộng là 13 + 3 + 3 + 1 + 5 + 3 = 28.


1
Tôi không hiểu bạn làm gì trong trường hợp chung sau khi ném bom vào các góc
Rịa

Ném bom vào góc không bao giờ hiệu quả hơn ném bom theo đường chéo vào trong từ góc.
psr

1
psr bạn hiểu nhầm bài của tôi, tôi ném bom theo đường chéo từ góc, đọc lại bài
Tyler Durden

11
@TylerDurden: điều này chỉ hoạt động vì ma trận nhỏ. Trên các ma trận lớn hơn, sau khi ném bom vào góc, bạn thường không thể cắt các cạnh nữa.
Lie Ryan

3

Điều này thực hiện một tìm kiếm rộng cho con đường ngắn nhất (một loạt các vụ đánh bom) thông qua "mê cung" vị trí này. Không, tôi không thể chứng minh rằng không có thuật toán nhanh hơn, xin lỗi.

#!/usr/bin/env python

M = ((1,2,3,4),
     (2,3,4,5),
     (5,2,7,4),
     (2,3,5,8))

def eachPossibleMove(m):
  for y in range(1, len(m)-1):
    for x in range(1, len(m[0])-1):
      if (0 == m[y-1][x-1] == m[y-1][x] == m[y-1][x+1] ==
               m[y][x-1]   == m[y][x]   == m[y][x+1] ==
               m[y+1][x-1] == m[y+1][x] == m[y+1][x+1]):
        continue
      yield x, y

def bomb(m, (mx, my)):
  return tuple(tuple(max(0, m[y][x]-1)
      if mx-1 <= x <= mx+1 and my-1 <= y <= my+1
      else m[y][x]
      for x in range(len(m[y])))
    for y in range(len(m)))

def findFirstSolution(m, path=[]):
#  print path
#  print m
  if sum(map(sum, m)) == 0:  # empty?
    return path
  for move in eachPossibleMove(m):
    return findFirstSolution(bomb(m, move), path + [ move ])

def findShortestSolution(m):
  black = {}
  nextWhite = { m: [] }
  while nextWhite:
    white = nextWhite
    nextWhite = {}
    for position, path in white.iteritems():
      for move in eachPossibleMove(position):
        nextPosition = bomb(position, move)
        nextPath = path + [ move ]
        if sum(map(sum, nextPosition)) == 0:  # empty?
          return nextPath
        if nextPosition in black or nextPosition in white:
          continue  # ignore, found that one before
        nextWhite[nextPosition] = nextPath

def main(argv):
  if argv[1] == 'first':
    print findFirstSolution(M)
  elif argv[1] == 'shortest':
    print findShortestSolution(M)
  else:
    raise NotImplementedError(argv[1])

if __name__ == '__main__':
  import sys
  sys.exit(main(sys.argv))

1
Thuật toán này sẽ tìm thấy số lần di chuyển ít nhất, nhưng nó có thể mất một thời gian rất dài. Bạn đã chạy cái này trên tập dữ liệu đã cho chưa? Điều đó sẽ đưa ra một đường cơ sở cho các thuật toán khác để so sánh với.
Ryan Amos

1
Một tập hợp con 5x4 của ma trận đã cho đã được giải trong khoảng 2 giây, 5x5 đã mất hơn 2 phút. Tôi chưa thử nhiều hơn ;-) Vâng, thuật toán này không được tối ưu hóa cho bất cứ điều gì ngoại trừ nhiệm vụ ban đầu: Tìm giải pháp ngắn nhất.
Alfe

2
Đó là vẻ đẹp của sự phức tạp theo cấp số nhân.
Ryan Amos

3

Có vẻ như một phương pháp lập trình tuyến tính có thể rất hữu ích ở đây.

Đặt P m xn là ma trận với các giá trị của các vị trí:

Ma trận vị trí

Bây giờ hãy xác định ma trận bom B (x, y) mxn , với 1 ≤ x ≤ m , 1 ≤ y ≤ n như dưới đây

Ma trận bom

trong một cách như vậy mà

Giá trị của các vị trí trong ma trận bom

Ví dụ:

B (3, 3)

Vì vậy, chúng tôi đang tìm kiếm một ma trận B m xn = [ b ij ]

  1. Có thể được định nghĩa là tổng của ma trận bom:

    B là tổng của ma trận bom

    ( q ij sẽ là số lượng bom chúng ta sẽ thả ở vị trí p ij )

  2. p ij - b ij 0 (để thành công hơn, chúng ta hãy nói nó là P - B 0 )

Ngoài ra, B nên tối thiểu hóa tổng tổng số lượng bom.

Chúng ta cũng có thể viết B là ma trận xấu xí phía trước:

B là ma trận tổng các đại lượng

và vì P - B 0 (có nghĩa là P B ), chúng ta có hệ bất đẳng thức tuyến tính khá sau đây:

Mối quan hệ giữa số lượng bom rơi và giá trị ở các vị trí

Được q mn x 1 định nghĩa là

Vector số lượng

p mn x 1 được định nghĩa là

Giá trị của P được phân phối dưới dạng vectơ

Chúng tôi có thể nói rằng chúng tôi có một hệ thống Hệ thống bên dưới được biểu thị dưới dạng sản phẩm của ma trận http://latex.codecogs.com/gif.doad?S%5Cmathbf%7Bq%7D&space;%5Cge&space;%5Cmathbf%7Bp%7DS m mn ma trận được đảo ngược để giải quyết hệ thống. Tôi đã không mở rộng nó một mình nhưng tôi tin rằng nó sẽ dễ dàng để làm điều đó trong mã.

Bây giờ, chúng tôi có một vấn đề tối thiểu có thể được nêu là

Hệ thống chúng ta phải giải quyết

Tôi tin rằng nó là một cái gì đó dễ dàng, gần như tầm thường để được giải quyết bằng một cái gì đó giống như thuật toán đơn giản (có tài liệu khá tuyệt vời về nó ). Tuy nhiên, tôi hầu như không biết lập trình tuyến tính hãy từ bỏ ở đây Nó có thể được rằng tôi đã làm điều gì sai tại một số điểm, hoặc là nó không thể đi xa hơn, nhưng tôi tin rằng con đường này cuối cùng có thể dẫn đến các giải pháp. Dù sao, tôi lo lắng cho phản hồi của bạn.

(Cảm ơn đặc biệt cho trang web tuyệt vời này để tạo hình ảnh từ các biểu thức LaTeX )


Bạn có chắc chắn sự bất bình đẳng của bạn không bị đảo ngược? Đó là Sq> = P? nghĩa là, tổng số lần một hình vuông bị ném bom lớn hơn hoặc bằng ma trận đã cho.
Darksky

1
Khi các biến của chương trình tuyến tính bị ràng buộc với số nguyên, chúng ta gọi đó là "lập trình tuyến tính số nguyên" (IP). Không giống như trường hợp liên tục, IP là NP-Complete. Thật không may, thuật toán đơn giản không giúp ích gì, trừ khi chấp nhận gần đúng. Và IP đã được đề cập trong một câu trả lời khác .
BlueRaja - Daniel Pflughoeft

@ BlueRaja-DannyPflughoeft đúng. "Despite the many crucial applications of this problem, and intense interest by researchers, no efficient algorithm is known for it.xem trang 254. Lập trình tuyến tính số nguyên là một vấn đề tính toán rất khó. Hy vọng duy nhất của chúng tôi là hiệu quả là khai thác các thuộc tính nội tại về ma trận của bạn S. Rốt cuộc nó không phải tùy tiện.
darksky 16/03/13

3

Giải pháp tham lam này dường như là chính xác :

Như đã nêu trong các bình luận, nó sẽ thất bại trong 2D. Nhưng có lẽ bạn có thể cải thiện nó.

Đối với 1D:
Nếu có ít nhất 2 số, bạn không cần phải bắn sang số ngoài cùng bên trái vì bắn sang số thứ hai không tệ hơn . Vì vậy, hãy bắn tới lần thứ hai, trong khi lần đầu tiên là 0, vì bạn phải làm điều đó. Di chuyển đến ô tiếp theo. Đừng quên tế bào cuối cùng.

Mã C ++:

void bombs(vector<int>& v, int i, int n){
    ans += n;
    v[i] -= n;
    if(i > 0)
        v[i - 1] -= n;
    if(i + 1< v.size())
        v[i + 1] -= n;
}

void solve(vector<int> v){
    int n = v.size();
    for(int i = 0; i < n;++i){
        if(i != n - 1){
            bombs(v, i + 1, v[i]);
        }
        else
            bombs(v, i, v[i])
    }
}

Vì vậy, đối với 2D:
Một lần nữa: bạn không cần phải quay ở hàng đầu tiên (nếu có hàng thứ hai). Vì vậy, bắn đến cái thứ hai. Giải quyết nhiệm vụ 1D cho hàng đầu tiên. (bởi vì bạn cần làm cho nó null). Đi xuống. Đừng quên hàng cuối cùng.


5
Một ví dụ : "0110","1110","1110". Bạn chỉ cần 1 phát bắn, nhưng tôi tin rằng thuật toán của bạn sẽ sử dụng 2.
maniek

2

Để giảm thiểu số lượng bom, chúng ta phải tối đa hóa hiệu quả của mỗi quả bom. Để đạt được điều này, trên mỗi bước chúng ta phải chọn mục tiêu tốt nhất. Đối với mỗi điểm tóm tắt nó và tám điểm lân cận - có thể được sử dụng như một lượng hiệu quả của việc ném bom điểm này. Điều này sẽ cung cấp gần với chuỗi bom tối ưu.

UPD : Chúng ta cũng nên lấy số lượng các zeros vào tài khoản, becouse bombin họ là không hiệu quả. Trong thực tế, vấn đề là để giảm thiểu số lượng số không được đánh. Nhưng chúng ta không thể biết làm thế nào bất kỳ bước nào đưa chúng ta đến gần hơn với mục tiêu này. Tôi đồng ý với ý kiến ​​rằng vấn đề là NP-đầy đủ. Tôi đưa ra một cách tiếp cận tham lam, sẽ đưa ra một câu trả lời gần với thực tế.


Điều này là không tối ưu. Ví dụ ngược lại : 1010101, 0010100(hàng trên cùng, hàng dưới cùng) Cách tiếp cận của bạn sẽ yêu cầu 3. Nó có thể được thực hiện trong 2.
Bí ẩn

2

Tôi tin rằng để giảm thiểu lượng bom bạn chỉ cần tối đa hóa lượng sát thương .. để điều đó xảy ra cần phải kiểm tra khu vực có lực mạnh nhất .. vì vậy trước tiên bạn hãy phân tích trường bằng hạt nhân 3x3 và kiểm tra tổng của nó là mạnh hơn .. và ném bom ở đó .. và làm cho đến khi trường bằng phẳng .. cho điều này nộp câu trả lời là 28

var oMatrix = [
[2,3,4,7,1],
[1,5,2,6,2],
[4,3,4,2,1],
[2,1,2,4,1],
[3,1,3,4,1],
[2,1,4,3,2],
[6,9,1,6,4]
]

var nBombs = 0;
do
{
    var bSpacesLeftToBomb = false;
    var nHigh = 0;
    var nCellX = 0;
    var nCellY = 0;
    for(var y = 1 ; y<oMatrix.length-1;y++) 
        for(var x = 1 ; x<oMatrix[y].length-1;x++)  
        {
            var nValue = 0;
            for(var yy = y-1;yy<=y+1;yy++)
                for(var xx = x-1;xx<=x+1;xx++)
                    nValue += oMatrix[yy][xx];

            if(nValue>nHigh)
            {
                nHigh = nValue;
                nCellX = x;
                nCellY = y; 
            }

        }
    if(nHigh>0)
    {
        nBombs++;

        for(var yy = nCellY-1;yy<=nCellY+1;yy++)
        {
            for(var xx = nCellX-1;xx<=nCellX+1;xx++)
            {
                if(oMatrix[yy][xx]<=0)
                    continue;
                oMatrix[yy][xx] = --oMatrix[yy][xx];
            }
        }
        bSpacesLeftToBomb = true;
    }
}
while(bSpacesLeftToBomb);

alert(nBombs+'bombs');

Đây là thuật toán tương tự như một vài câu trả lời khác, nhưng muộn hơn nhiều.
psr

@psr Không chỉ vậy. Nó không phải là tối ưu.
Bí ẩn

Tôi đã đăng nó, bởi vì, trong khi thuật toán này được đề xuất, tôi không tìm thấy bài viết nào về mã hay "prof of concept". vì vậy tôi nghĩ rằng nó có thể giúp ích cho sự thận trọng .. nhưng .. btw @Mysticial bạn có prof rằng có một cách tối ưu hơn không?
CaldasGSM

@CaldasGSM Không phải lo lắng, vấn đề ban đầu (không có trình tự) là khó khăn. Cho đến nay chỉ có một câu trả lời giải quyết nó một cách tối ưu, nhưng nó chạy theo thời gian theo cấp số nhân.
Bí ẩn

2

Đây là một giải pháp tổng quát hóa các tính chất tốt của các góc.

Giả sử rằng chúng ta có thể tìm thấy một điểm rơi hoàn hảo cho một trường nhất định, đó là cách tốt nhất để giảm giá trị trong đó. Sau đó, để tìm số lượng bom tối thiểu được thả, một bản thảo đầu tiên của thuật toán có thể là (mã được sao chép-dán từ một triển khai ruby):

dropped_bomb_count = 0
while there_are_cells_with_non_zero_count_left
  coordinates = choose_a_perfect_drop_point
  drop_bomb(coordinates)
  dropped_bomb_count += 1
end
return dropped_bomb_count

Thách thức là choose_a_perfect_drop_point. Đầu tiên, hãy xác định điểm rơi hoàn hảo là gì.

  • Một điểm rơi để (x, y)giảm giá trị trong (x, y). Nó cũng có thể làm giảm giá trị trong các ô khác.
  • Một điểm thả một cho (x, y)tốt hơn so với mức giảm điểm b cho (x, y)nếu nó làm giảm giá trị trong một superset đúng đắn của các tế bào b giảm.
  • Điểm rơi là tối đa nếu không có điểm rơi nào tốt hơn.
  • Hai điểm thả cho (x, y)tương đương nếu họ giảm cùng một tập hợp của các tế bào.
  • Một điểm rơi cho (x, y)hoàn hảo nếu nó tương đương với tất cả các điểm rơi tối đa cho (x, y).

Nếu có một điểm rơi hoàn hảo cho (x, y), bạn không thể giảm giá trị (x, y)hiệu quả hơn là thả một quả bom vào một trong những điểm thả hoàn hảo cho (x, y).

Một điểm rơi hoàn hảo cho một trường nhất định là một điểm rơi hoàn hảo cho bất kỳ ô nào của nó.

Dưới đây là một vài ví dụ:

1 0 1 0 0
0 0 0 0 0
1 0 0 0 0
0 0 0 0 0
0 0 0 0 0

Điểm rơi hoàn hảo cho ô (0, 0)(chỉ số dựa trên zero) là (1, 1). Tất cả các điểm thả khác (1, 1), đó là (0, 0), (0, 1)(1, 0), giảm tế bào ít hơn.

0 0 0 0 0
0 0 0 0 0
0 0 1 0 0
0 0 0 0 0
0 0 0 0 0

Sự sụt giảm điểm hoàn hảo cho các tế bào (2, 2)(chỉ số zero-based) là (2, 2), và cũng có tất cả các tế bào xung quanh (1, 1), (1, 2), (1, 3), (2, 1), (2, 3), (3, 1), (3, 2), và (3, 3).

0 0 0 0 1
0 0 0 0 0
0 0 1 0 0
0 0 0 0 0
0 0 0 0 0

một điểm rơi hoàn hảo cho ô (2, 2)(3, 1): Nó làm giảm giá trị trong (2, 2)và giá trị trong (4, 0). Tất cả các điểm rơi khác (2, 2)không phải là tối đa, vì chúng giảm một ô ít hơn. Điểm rơi hoàn hảo cho (2, 2)cũng là điểm rơi hoàn hảo cho (4, 0), và nó là điểm rơi hoàn hảo duy nhất cho lĩnh vực này. Nó dẫn đến giải pháp hoàn hảo cho lĩnh vực này (một lần thả bom).

1 0 0 0 0
0 0 0 0 0
0 0 1 0 0
0 0 0 0 0
1 0 0 0 0

Không có điểm rơi hoàn hảo cho (2, 2): Cả (1, 1)(1, 3)giảm (2, 2)và một ô khác (chúng là điểm rơi tối đa cho (2, 2)), nhưng chúng không tương đương. Tuy nhiên, (1, 1)là một điểm rơi hoàn hảo cho (0, 0), và (1, 3)là một điểm rơi hoàn hảo cho (0, 4).

Với định nghĩa về điểm rơi hoàn hảo và một thứ tự kiểm tra nhất định, tôi nhận được kết quả sau đây cho ví dụ trong câu hỏi:

Drop bomb on 1, 1
Drop bomb on 1, 1
Drop bomb on 1, 5
Drop bomb on 1, 5
Drop bomb on 1, 5
Drop bomb on 1, 6
Drop bomb on 1, 2
Drop bomb on 1, 2
Drop bomb on 0, 6
Drop bomb on 0, 6
Drop bomb on 2, 1
Drop bomb on 2, 5
Drop bomb on 2, 5
Drop bomb on 2, 5
Drop bomb on 3, 1
Drop bomb on 3, 0
Drop bomb on 3, 0
Drop bomb on 3, 0
Drop bomb on 3, 0
Drop bomb on 3, 0
Drop bomb on 3, 4
Drop bomb on 3, 4
Drop bomb on 3, 3
Drop bomb on 3, 3
Drop bomb on 3, 6
Drop bomb on 3, 6
Drop bomb on 3, 6
Drop bomb on 4, 6
28

Tuy nhiên, thuật toán chỉ hoạt động nếu có ít nhất một điểm rơi hoàn hảo sau mỗi bước. Có thể xây dựng các ví dụ khi không có điểm rơi hoàn hảo:

0 1 1 0
1 0 0 1
1 0 0 1
0 1 1 0

Đối với những trường hợp này, chúng tôi có thể sửa đổi thuật toán để thay vì điểm rơi hoàn hảo, chúng tôi chọn tọa độ với lựa chọn tối thiểu là điểm rơi tối đa, sau đó tính toán mức tối thiểu cho mỗi lựa chọn. Trong trường hợp trên, tất cả các ô có giá trị có hai điểm rơi tối đa. Ví dụ, (0, 1)có điểm rơi tối đa (1, 1)(1, 2). Chọn một trong hai và sau đó tính toán mức tối thiểu dẫn đến kết quả này:

Drop bomb on 1, 1
Drop bomb on 2, 2
Drop bomb on 1, 2
Drop bomb on 2, 1
2

Đây là khá nhiều thuật toán tham lam được trình bày ở trên.
darksky

Vâng, nó cũng là một thuật toán tham lam, nhưng thay vì tập trung vào các góc và cạnh, tôi đã xác định cách chọn điểm rơi tiếp theo. Với hình vuông ví dụ 5x7, thật dễ dàng để nói về các góc, trên trường 1000x1000, không quá nhiều. Nếu bạn kiểm tra thứ tự mà thuật toán của tôi xóa trường, nó không phải từ bên ngoài vào, mà từ trên xuống dưới / từ trái sang phải.
Tammo Freese

2

Đây là một ý tưởng khác:

Hãy bắt đầu bằng cách chỉ định một trọng lượng cho mỗi khoảng trống trên bảng để giảm số lượng bằng cách thả một quả bom ở đó. Vì vậy, nếu không gian có số khác không, nó sẽ có một điểm và nếu bất kỳ không gian nào bên cạnh nó có số khác không, nó sẽ có thêm một điểm. Vì vậy, nếu có lưới 1000 x 1000, chúng ta có trọng số được gán cho mỗi 1 triệu khoảng trắng.

Sau đó sắp xếp danh sách các không gian theo trọng lượng và ném bom vào vị trí có trọng lượng cao nhất. Điều này đang nhận được nhiều tiếng nổ nhất cho buck của chúng tôi, có thể nói như vậy.

Sau đó, cập nhật trọng lượng của mọi không gian có trọng lượng bị ảnh hưởng bởi bom. Đây sẽ là không gian bạn ném bom, và bất kỳ không gian nào liền kề với nó, và bất kỳ không gian nào liền kề với không gian đó. Nói cách khác, bất kỳ không gian nào có thể có giá trị của nó giảm xuống 0 khi ném bom hoặc giá trị của không gian lân cận giảm xuống không.

Sau đó, sắp xếp lại các không gian danh sách theo trọng lượng. Vì chỉ có một tập hợp nhỏ các không gian có trọng lượng thay đổi khi ném bom, bạn sẽ không cần phải sử dụng toàn bộ danh sách, chỉ cần di chuyển những thứ đó trong danh sách.

Ném bom không gian trọng lượng cao nhất mới, và lặp lại quy trình.

Điều này đảm bảo rằng mọi vụ đánh bom đều làm giảm càng nhiều khoảng trống càng tốt (về cơ bản, nó đánh vào càng ít khoảng trống đã bằng 0), do đó, nó sẽ là tối ưu, ngoại trừ việc chúng có thể có trọng lượng. Vì vậy, bạn có thể cần phải thực hiện một số theo dõi trở lại khi có một cà vạt cho trọng lượng hàng đầu. Tuy nhiên, chỉ có một chiếc cà vạt cho các vấn đề trọng lượng hàng đầu, chứ không phải các loại cà vạt khác, vì vậy hy vọng nó không quá nhiều theo dõi ngược.

Chỉnh sửa: Ví dụ của Mysticial dưới đây chứng minh rằng trên thực tế điều này không được đảm bảo là tối ưu, bất kể quan hệ về trọng lượng. Trong một số trường hợp, việc giảm trọng lượng càng nhiều càng tốt trong một bước nhất định thực sự khiến cho những quả bom còn lại quá dàn trải để đạt được mức giảm cao nhất sau bước thứ hai như bạn có thể có với sự lựa chọn ít tham lam hơn trong bước đầu tiên. Tôi đã hơi hiểu lầm bởi khái niệm rằng kết quả là không nhạy cảm với thứ tự của các vụ đánh bom. Họ không nhạy cảm với thứ tự mà bạn có thể thực hiện bất kỳ loạt vụ đánh bom nào và phát lại chúng từ đầu theo một thứ tự khác và kết thúc với cùng một bảng kết quả. Nhưng nó không theo đó mà bạn có thể xem xét từng vụ đánh bom một cách độc lập. Hoặc, ít nhất, mỗi vụ đánh bom phải được xem xét theo cách tính đến mức độ nó thiết lập bảng cho các vụ đánh bom tiếp theo.


nó vẫn sẽ là rất nhiều quay trở lại, lúc đầu vì các trường có rất ít số không, trọng số của hầu hết các ô sẽ là tất cả các số liệu.
Lie Ryan

Vâng, đó là một điểm tốt, vì không có phạm vi lớn trong các trọng số có thể (chỉ từ 0 đến 9).
Tim Goodman

Tôi vẫn không chắc chắn 100% việc quay lại là cần thiết như thế nào ... có thể là hướng dẫn để xây dựng một mạng lưới trong đó một lựa chọn ném bom tham lam không thua kém một lựa chọn ném bom tham lam khác. Có lẽ có một số cách nhất quán để dự đoán cái nào tốt hơn.
Tim Goodman

Thật ra, tôi thấy Đại tá Panic đã làm điều này trong câu trả lời của mình. Lý do một lựa chọn tham lam có thể tốt hơn so với lựa chọn khác là vì một người để lại những con số còn lại trải rộng hơn.
Tim Goodman

3
1010101, 0010100có thể là một ví dụ chứng minh cách tiếp cận này là không tối ưu. Cách tiếp cận này đòi hỏi 3. Nó có thể được thực hiện trong 2.
Mystical

1

Chà, giả sử chúng ta đánh số các vị trí bảng 1, 2, ..., nx m. Bất kỳ chuỗi thả bom nào cũng có thể được biểu diễn bằng một chuỗi số trong tập hợp này, trong đó các số có thể lặp lại. Tuy nhiên, hiệu ứng trên bảng là như nhau bất kể bạn thả bom theo thứ tự nào, vì vậy thực sự mọi lựa chọn thả bom đều có thể được trình bày dưới dạng danh sách các số nxm, trong đó số đầu tiên đại diện cho số bom rơi ở vị trí 1 , số thứ hai đại diện cho số lượng bom rơi ở vị trí 2, v.v ... Hãy gọi danh sách số nxm này là "chìa khóa".

Trước tiên, bạn có thể thử tính toán tất cả các trạng thái của bảng do thả 1 quả bom, sau đó sử dụng các trạng thái này để tính toán tất cả các trạng thái của bảng do 2 lần thả bom, v.v ... cho đến khi bạn nhận được tất cả số không. Nhưng ở mỗi bước, bạn sẽ lưu trữ các trạng thái bằng khóa tôi đã xác định ở trên, vì vậy bạn có thể sử dụng các kết quả này để tính bước tiếp theo (phương pháp "lập trình động").

Nhưng tùy thuộc vào kích thước của n, m và các số trong lưới, yêu cầu bộ nhớ của phương pháp này có thể quá mức. Bạn có thể vứt bỏ tất cả các kết quả cho việc thả bom N sau khi bạn đã tính tất cả các kết quả cho N + 1, vì vậy có một số tiền tiết kiệm ở đó. Và tất nhiên, bạn không thể lưu trữ bất cứ thứ gì với chi phí phải mất nhiều thời gian hơn - phương pháp lập trình động trao đổi bộ nhớ cho tốc độ.


1
Nghi ngờ điều đó là có thể vì (nếu tôi hiểu bạn chính xác). n = m. Tôi cần 10 ^ 6 int con trỏ tới (10 ^ 6) ^ 2 int cell. Tôi có nhiều bảng như chìa khóa trong bảng. 10 ^ 12 nghi ngờ tôi có thể phân bổ rất nhiều trong máy 32 bit.
abc

Vâng, tôi vừa thấy nhận xét của bạn về các bảng lên tới 1000 bởi 1000. Vì vậy, đó là một triệu ints cho trạng thái của mỗi bảng, cộng với một triệu ints cho số lượng bom rơi trên mỗi vị trí. Vì vậy, với mỗi bảng bạn lưu trữ, bạn cần 2 triệu ints, và có rất nhiều bảng có thể ...
Tim Goodman

Tôi đã thêm một câu trả lời thứ hai sử dụng một cách tiếp cận khác.
Tim Goodman

1
Vâng. Một cách tiếp cận vũ phu, nhưng tôi đoán không thực tế cho một bảng lớn.
Tim Goodman

@Kostek, tại sao ước tính thấp như vậy? Nó giống như bộ nhớ k ^ (m * n) với k là giới hạn cho các số mà bảng ban đầu được điền vào.
Rotsor

1

Nếu bạn muốn giải pháp tối ưu tuyệt đối để làm sạch bảng, bạn sẽ phải sử dụng quay lui cổ điển, nhưng nếu ma trận rất lớn, sẽ mất nhiều thời gian để tìm giải pháp tốt nhất, nếu bạn muốn một giải pháp tối ưu "có thể", bạn có thể sử dụng thuật toán tham lam , nếu bạn cần trợ giúp viết thuật toán, tôi có thể giúp bạn

Hãy nghĩ về nó đó là cách tốt nhất. Tạo một ma trận khác ở đó bạn lưu trữ các điểm bạn loại bỏ bằng cách thả một quả bom vào đó sau đó chọn ô có điểm tối đa và thả quả bom ở đó cập nhật ma trận điểm và tiếp tục. Thí dụ:

2 3 5 -> (2+(1*3)) (3+(1*5)) (5+(1*3))
1 3 2 -> (1+(1*4)) (3+(1*7)) (2+(1*4))
1 0 2 -> (1+(1*2)) (0+(1*5)) (2+(1*2))

giá trị ô +1 cho mỗi ô liền kề có giá trị cao hơn 0


7
sẽ phải sử dụng quay lui cổ điển . Bạn có một bằng chứng cho điều này?
Shahbaz

Tôi không chắc. Đó là từ cuộc thi tôi đang chuẩn bị (từ năm trước). Giới hạn là 1 <= n, m <= 1000 (không biết có lớn hay không). Dù sao bạn cũng cần câu trả lời chính xác (tương tự như cuộc thi CERC, v.v.). Giới hạn thời gian không được đưa ra, không có câu trả lời, không có giải pháp trên trang cuộc thi.
abc

tất cả các thuật toán khác sẽ cung cấp cho bạn một giải pháp tối ưu khả thi nhưng cho đến khi bạn thử tất cả (quay lại), bạn sẽ không biết liệu giải pháp đó có tốt nhất hay không
cosmin.danisor

2
bạn không cần phải sử dụng backtracking vì nó là sự kết hợp bạn tìm kiếm, không phải permutaion. Thứ tự ném bom không quan trọng
Luka Rahne

sau đó bạn có thể thử sử dụng một biến thể của tham lam. ở mỗi bước tạo một ma trận mới và mọi điểm sẽ có giá trị ô của anh ta + 1 cho mỗi ô bên cạnh> 0 theo cách này sẽ chọn nơi tốt hơn để thả bom tiếp theo
cosmin.danisor

1

Lực lượng vũ phu !

Tôi biết nó không hiệu quả, nhưng ngay cả khi bạn tìm thấy một thuật toán nhanh hơn, bạn luôn có thể kiểm tra kết quả này để biết nó chính xác đến mức nào.

Sử dụng một số đệ quy, như thế này:

void fn(tableState ts, currentlevel cl)
{
  // first check if ts is all zeros yet, if not:
  //
  // do a for loop to go through all cells of ts, 
  // for each cell do a bomb, and then
  // call: 
  // fn(ts, cl + 1);

}

Bạn có thể thực hiện việc này hiệu quả hơn bằng cách lưu vào bộ đệm, nếu cách khác dẫn đến cùng một kết quả, bạn không nên lặp lại các bước tương tự.

Để giải thích:

Nếu ném bom ô 1,3,5 dẫn đến kết quả tương tự như ném bom ô 5,3,1, thì bạn không nên thực hiện lại tất cả các bước tiếp theo cho cả hai trường hợp, chỉ 1 là đủ, bạn nên lưu trữ ở đâu đó tất cả bảng trạng thái và sử dụng kết quả của nó.

Băm các số liệu thống kê bảng có thể được sử dụng để so sánh nhanh.


1
  1. Không bao giờ ném bom biên giới (trừ khi hình vuông không có hàng xóm không có trật tự)
  2. Không có góc.
  3. Để góc không, thả giá trị của góc một đường chéo đi chéo (hàng xóm duy nhất không có thứ tự)
  4. Điều này sẽ tạo ra những góc mới. Tới 2

Chỉnh sửa: không nhận thấy rằng Kostek đề xuất cách tiếp cận gần như giống nhau, vì vậy bây giờ tôi đưa ra tuyên bố mạnh mẽ hơn: Nếu các góc cần xóa được chọn luôn ở lớp ngoài cùng, thì đó là tối ưu.

Trong ví dụ của OP: thả 2 (dưới dạng 1 + 1 hoặc 2) vào bất cứ thứ gì khác ngoài 5 không dẫn đến việc đánh bất kỳ ô vuông nào rơi vào 5 sẽ trúng. Vì vậy, chúng ta chỉ cần giảm 2 trên 5 (và 6 ở phía dưới bên trái 1 ...)

Sau này, chỉ có một cách làm thế nào để xóa (ở góc trên bên trái) những gì ban đầu là 1 (bây giờ là 0), và đó là bằng cách giảm 0 trên B3 (excel như ký hiệu). Và như thế.

Chỉ sau khi xóa toàn bộ cột A và E và 1 và 7 hàng, hãy bắt đầu xóa một lớp sâu hơn.

Chỉ xem xét xóa những người cố ý xóa, xóa 0 góc giá trị không tốn kém gì và đơn giản hóa suy nghĩ về nó.

Bởi vì tất cả các quả bom rơi theo cách này phải được thả và điều này dẫn đến các trường bị xóa, đó là giải pháp tối ưu.


Sau khi ngủ ngon tôi nhận ra rằng điều này không đúng. Xem xét

  ABCDE    
1 01000
2 10000
3 00000
4 00000

Cách tiếp cận của tôi sẽ thả bom vào B3 và C2, khi thả vào B2 là đủ


Nhưng điều này là tối ưu mặc dù?
Bí ẩn

7
Các góc mới có thể đánh bom theo 2 cách (nếu hầu hết các điểm góc chứa thấp nhất trong tất cả 4 giá trị). Cái nào là ném bom tối ưu?
abc

Tôi đã suy nghĩ về một cách tiếp cận tương tự, và khi bạn gặp phải một tình huống như Kostek đã mô tả, sau đó bắt đầu sử dụng quay lui ...
Karoly Horvath

Các góc cung cấp cho bạn số lượng bom tối thiểu được thả trên hình vuông chéo. Nhưng một khi bạn bỏ chúng, gạch viền tiếp theo sẽ không nhất thiết phải có điểm tối ưu rõ ràng. Đó là một cách tốt để giảm không gian tìm kiếm mặc dù.
Eugene

Điều gì về việc chọn đường chéo góc mới mang lại tổng số cao nhất trong hộp hit?
Thẩm phán Maygarden

1

Đây là giải pháp của tôi .. Tôi sẽ không viết mã ra vì tôi không có thời gian, nhưng tôi tin rằng điều này sẽ tạo ra số lần di chuyển tối ưu mỗi lần - mặc dù tôi không chắc nó sẽ hiệu quả như thế nào khi tìm kiếm các điểm để đánh bom.

Thứ nhất, như @Luka Rahne đã nêu trong một trong những bình luận, thứ tự bạn đánh bom không quan trọng - chỉ có sự kết hợp.

Thứ hai, như nhiều người khác đã tuyên bố, ném bom 1-off đường chéo từ các góc là tối ưu vì nó chạm nhiều điểm hơn các góc.

Điều này tạo ra nền tảng cho phiên bản thuật toán của tôi: Chúng ta có thể đánh bom '1-off từ các góc đầu tiên hoặc cuối cùng, không thành vấn đề (về lý thuyết) Chúng ta đánh bom chúng trước vì nó giúp cho các quyết định sau dễ dàng hơn (trong thực tế) Chúng tôi ném bom điểm ảnh hưởng đến nhiều điểm nhất, đồng thời ném bom những góc đó.

Hãy xác định Điểm Kháng cự là các điểm trong bảng có nhiều điểm không bị đánh bom nhất + số 0 lớn nhất xung quanh chúng

các điểm không ném bom có thể được định nghĩa là các điểm không tồn tại trong phạm vi hiện tại của bảng chúng tôi đang xem xét.

Tôi cũng sẽ xác định 4 giới hạn sẽ xử lý phạm vi của chúng tôi: Top = 0, Left = 0, bottom = k, right = j. (giá trị để bắt đầu)

Cuối cùng, tôi sẽ xác định bom tối ưu là bom được thả vào các điểm tiếp giáp với các điểm kháng cự và chạm vào (1) điểm kháng cự có giá trị cao nhất và (2) số điểm lớn nhất có thể.

Về cách tiếp cận - rõ ràng là chúng tôi đang làm việc từ bên ngoài. Chúng tôi sẽ có thể làm việc với 4 'máy bay ném bom' cùng một lúc.

Những điểm đầu tiên của kháng chiến rõ ràng là góc của chúng tôi. Các điểm 'ngoài giới hạn' không bị đánh bom (có 5 điểm ngoài phạm vi cho mỗi góc). Vì vậy, chúng tôi ném bom các điểm theo đường chéo một góc đầu tiên.

Thuật toán:

  1. Tìm 4 điểm bom tối ưu.
  2. Nếu một điểm ném bom đang ném bom một điểm kháng cự chạm 2 điểm (tức là một góc), hãy ném bom đến điểm đó bằng 0. Nếu không, hãy ném bom cho đến khi một trong những điểm kháng cự chạm vào điểm bom tối ưu là 0.
  3. cho mỗi ràng buộc: if (sum (ràng buộc) == 0) trước ràng buộc

lặp lại cho đến khi TOP = BOTTOM và TRÁI = PHẢI

Tôi sẽ cố gắng viết mã thực tế sau


1

Bạn có thể sử dụng quy hoạch không gian nhà nước. Ví dụ: sử dụng A * (hoặc một trong các biến thể của nó) kết hợp với một heuristic f = g + hnhư thế này:

  • g: số lượng bom rơi cho đến nay
  • h: tổng trên tất cả các giá trị của lưới chia cho 9 (đó là kết quả tốt nhất, có nghĩa là chúng ta có một heuristic được chấp nhận)

1

Tôi đã nhận được 28 di chuyển là tốt. Tôi đã sử dụng hai bài kiểm tra cho bước tiếp theo tốt nhất: đầu tiên là động thái tạo ra số tiền tối thiểu cho bảng. Thứ hai, đối với các khoản tiền bằng nhau, di chuyển tạo ra mật độ tối đa, được xác định là:

number-of-zeros / number-of-groups-of-zeros

Đây là Haskell. "Giải quyết bảng" cho thấy giải pháp của động cơ. Bạn có thể chơi trò chơi bằng cách nhập "chính", sau đó nhập điểm mục tiêu, "tốt nhất" để được đề xuất hoặc "thoát" để thoát.

OUTPUT:
* Main> giải quyết bảng
[(4,4), (3,6), (3,3), (2,2), (2,2), (4,6), (4,6), (2,6), (3,2), (4.2), (2,6), (3,3), (4,3), (2,6), (4.2), (4) , 6), (4,6), (3,6), (2,6), (2,6), (2,4), (2,4), (2,6), (3,6) ), (4.2), (4.2), (4.2), (4.2)]

import Data.List
import Data.List.Split
import Data.Ord
import Data.Function(on)

board = [2,3,4,7,1,
         1,5,2,6,2,
         4,3,4,2,1,
         2,1,2,4,1,
         3,1,3,4,1,
         2,1,4,3,2,
         6,9,1,6,4]

n = 5
m = 7

updateBoard board pt =
  let x = fst pt
      y = snd pt
      precedingLines = replicate ((y-2) * n) 0
      bomb = concat $ replicate (if y == 1
                                    then 2
                                    else min 3 (m+2-y)) (replicate (x-2) 0 
                                                         ++ (if x == 1 
                                                                then [1,1]
                                                                else replicate (min 3 (n+2-x)) 1)
                                                                ++ replicate (n-(x+1)) 0)
  in zipWith (\a b -> max 0 (a-b)) board (precedingLines ++ bomb ++ repeat 0)

showBoard board = 
  let top = "   " ++ (concat $ map (\x -> show x ++ ".") [1..n]) ++ "\n"
      chunks = chunksOf n board
  in putStrLn (top ++ showBoard' chunks "" 1)
       where showBoard' []     str count = str
             showBoard' (x:xs) str count =
               showBoard' xs (str ++ show count ++ "." ++ show x ++ "\n") (count+1)

instances _ [] = 0
instances x (y:ys)
  | x == y    = 1 + instances x ys
  | otherwise = instances x ys

density a = 
  let numZeros = instances 0 a
      groupsOfZeros = filter (\x -> head x == 0) (group a)
  in if null groupsOfZeros then 0 else numZeros / fromIntegral (length groupsOfZeros)

boardDensity board = sum (map density (chunksOf n board))

moves = [(a,b) | a <- [2..n-1], b <- [2..m-1]]               

bestMove board = 
  let lowestSumMoves = take 1 $ groupBy ((==) `on` snd) 
                              $ sortBy (comparing snd) (map (\x -> (x, sum $ updateBoard board x)) (moves))
  in if null lowestSumMoves
        then (0,0)
        else let lowestSumMoves' = map (\x -> fst x) (head lowestSumMoves) 
             in fst $ head $ reverse $ sortBy (comparing snd) 
                (map (\x -> (x, boardDensity $ updateBoard board x)) (lowestSumMoves'))   

solve board = solve' board [] where
  solve' board result
    | sum board == 0 = result
    | otherwise      = 
        let best = bestMove board 
        in solve' (updateBoard board best) (result ++ [best])

main :: IO ()
main = mainLoop board where
  mainLoop board = do 
    putStrLn ""
    showBoard board
    putStr "Pt: "
    a <- getLine
    case a of 
      "quit"    -> do putStrLn ""
                      return ()
      "best"    -> do putStrLn (show $ bestMove board)
                      mainLoop board
      otherwise -> let ws = splitOn "," a
                       pt = (read (head ws), read (last ws))
                   in do mainLoop (updateBoard board pt)

1

Dường như có một cấu trúc phù hợp không phân nhánh ở đây. Hãy xem xét ví dụ sau:

0010000
1000100
0000001
1000000
0000001
1000100
0010000

Giải pháp tối ưu cho trường hợp này có kích thước 5 vì đó là kích thước của một đỉnh tối thiểu của các đỉnh của chu kỳ 9 theo các cạnh của nó.

Trường hợp này, đặc biệt, cho thấy rằng thư giãn lập trình tuyến tính mà một số người đã đăng không chính xác, không hoạt động và tất cả những điều xấu khác. Tôi khá chắc chắn rằng tôi có thể giảm "che các đỉnh của đồ thị khối phẳng của mình xuống càng ít cạnh càng tốt" cho vấn đề của bạn, điều này khiến tôi nghi ngờ liệu có bất kỳ giải pháp tham lam / leo đồi nào sẽ hoạt động hay không.

Tôi không thấy một cách để giải quyết điều này trong thời gian đa thức trong trường hợp xấu nhất. Có thể có một giải pháp tìm kiếm nhị phân và DP rất thông minh mà tôi không thấy.

EDIT : Tôi thấy rằng cuộc thi ( http://deadline24.pl ) là ngôn ngữ bất khả tri; họ gửi cho bạn một loạt các tập tin đầu vào và bạn gửi chúng đầu ra. Vì vậy, bạn không cần một cái gì đó chạy trong thời gian đa thức tồi tệ nhất. Đặc biệt, bạn có thể nhìn vào đầu vào !

Có một loạt các trường hợp nhỏ trong đầu vào. Sau đó, có trường hợp 10x1000, trường hợp 100x100 và trường hợp 1000x1000. Ba trường hợp lớn đều cư xử rất tốt. Các mục liền kề theo chiều ngang thường có cùng giá trị. Trên một cỗ máy tương đối khó khăn, tôi có thể giải quyết tất cả các trường hợp bằng cách bắt buộc sử dụng CPLEX chỉ trong vài phút. Tôi đã gặp may mắn trên 1000x1000; thư giãn LP xảy ra để có một giải pháp tối ưu không thể thiếu. Các giải pháp của tôi đồng ý với các .anstệp được cung cấp trong gói dữ liệu thử nghiệm.

Tôi cá là bạn có thể sử dụng cấu trúc của đầu vào theo cách trực tiếp hơn nhiều so với tôi đã làm nếu bạn nhìn vào nó; có vẻ như bạn chỉ có thể bỏ qua hàng đầu tiên, hoặc hai hoặc ba liên tục cho đến khi bạn không còn gì. (Có vẻ như, trong 1000x1000, tất cả các hàng đều không tăng? Tôi đoán đó là "phần B" của bạn đến từ đâu?)


Vâng Đôi khi tôi chỉ bỏ qua phần "không liên quan" của văn bản. Chỉ cần một thời gian ngắn nhận được ý tưởng và như vậy. Lần này về cơ bản nó thay đổi cấp độ từ dễ đến khó như địa ngục: P Dù sao tôi biết bạn có thể cố gắng tạo ra một heuristic có bộ đầu vào "đã biết". Mặt khác tôi chỉ nghĩ rằng nếu câu trả lời không phải là tỷ lệ phần trăm thì phải có một số thuật toán sẽ dễ dàng thực hiện trong 5h. Tất cả tôi tìm thấy có sự phức tạp quá lớn. Sau đó tôi đọc nó cẩn thận hơn, khi ai đó hỏi về nguồn gốc :)
abc

Chúng ta có thể nói nhờ có nhiều người có vấn đề tốt, để suy nghĩ, nhưng nghi ngờ nó có thể được thực hiện trong thời gian đa thức. Thật buồn cười khi một ràng buộc đơn giản, thay đổi mức độ nhiệm vụ từ dễ thành không thể.
abc

@Kostek: Xin lỗi nếu tôi không rõ ràng. Tôi ... khá tệ trong việc giải thích cao độ ở một mức độ phù hợp cho khán giả. :) Tôi đã không rõ nơi nào?
tmyklebu

1

Tôi không thể nghĩ ra cách tính con số thực tế mà không cần tính toán chiến dịch ném bom bằng cách sử dụng phương pháp phỏng đoán tốt nhất của mình và hy vọng tôi có được kết quả hợp lý.

Vì vậy, phương pháp của tôi là tính toán một chỉ số hiệu quả ném bom cho mỗi ô, ném bom vào ô có giá trị cao nhất, .... lặp lại quy trình cho đến khi tôi làm phẳng mọi thứ. Một số người đã ủng hộ việc sử dụng thiệt hại tiềm năng đơn giản (nghĩa là điểm từ 0 đến 9) làm số liệu, nhưng điều đó không đạt được bằng cách đập các ô có giá trị cao và không sử dụng chồng chéo thiệt hại. Tôi sẽ tính toán cell value - sum of all neighbouring cells, đặt lại bất kỳ số dương nào về 0 và sử dụng giá trị tuyệt đối của bất kỳ giá trị âm nào. Theo trực giác, số liệu này sẽ đưa ra lựa chọn giúp tối đa hóa thiệt hại chồng chéo lên các ô có số lượng cao thay vì đập trực tiếp vào các ô đó.

Mã dưới đây đạt đến sự phá hủy hoàn toàn của trường thử nghiệm trong 28 quả bom (lưu ý rằng sử dụng thiệt hại tiềm năng như số liệu mang lại 31!).

using System;
using System.Collections.Generic;
using System.Linq;

namespace StackOverflow
{
  internal class Program
  {
    // store the battle field as flat array + dimensions
    private static int _width = 5;
    private static int _length = 7;
    private static int[] _field = new int[] {
        2, 3, 4, 7, 1,
        1, 5, 2, 6, 2,
        4, 3, 4, 2, 1,
        2, 1, 2, 4, 1,
        3, 1, 3, 4, 1,
        2, 1, 4, 3, 2,
        6, 9, 1, 6, 4
    };
    // this will store the devastation metric
    private static int[] _metric;

    // do the work
    private static void Main(string[] args)
    {
        int count = 0;

        while (_field.Sum() > 0)
        {
            Console.Out.WriteLine("Round {0}:", ++count);
            GetBlastPotential();
            int cell_to_bomb = FindBestBombingSite();
            PrintField(cell_to_bomb);
            Bomb(cell_to_bomb);
        }
        Console.Out.WriteLine("Done in {0} rounds", count);
    } 

    // convert 2D position to 1D index
    private static int Get1DCoord(int x, int y)
    {
        if ((x < 0) || (y < 0) || (x >= _width) || (y >= _length)) return -1;
        else
        {
            return (y * _width) + x;
        }
    }

    // Convert 1D index to 2D position
    private static void Get2DCoord(int n, out int x, out int y)
    {
        if ((n < 0) || (n >= _field.Length))
        {
            x = -1;
            y = -1;
        }
        else
        {
            x = n % _width;
            y = n / _width;
        }
    }

    // Compute a list of 1D indices for a cell neighbours
    private static List<int> GetNeighbours(int cell)
    {
        List<int> neighbours = new List<int>();
        int x, y;
        Get2DCoord(cell, out x, out y);
        if ((x >= 0) && (y >= 0))
        {
            List<int> tmp = new List<int>();
            tmp.Add(Get1DCoord(x - 1, y - 1));
            tmp.Add(Get1DCoord(x - 1, y));
            tmp.Add(Get1DCoord(x - 1, y + 1));
            tmp.Add(Get1DCoord(x, y - 1));
            tmp.Add(Get1DCoord(x, y + 1));
            tmp.Add(Get1DCoord(x + 1, y - 1));
            tmp.Add(Get1DCoord(x + 1, y));
            tmp.Add(Get1DCoord(x + 1, y + 1));

            // eliminate invalid coords - i.e. stuff past the edges
            foreach (int c in tmp) if (c >= 0) neighbours.Add(c);
        }
        return neighbours;
    }

    // Compute the devastation metric for each cell
    // Represent the Value of the cell minus the sum of all its neighbours
    private static void GetBlastPotential()
    {
        _metric = new int[_field.Length];
        for (int i = 0; i < _field.Length; i++)
        {
            _metric[i] = _field[i];
            List<int> neighbours = GetNeighbours(i);
            if (neighbours != null)
            {
                foreach (int j in neighbours) _metric[i] -= _field[j];
            }
        }
        for (int i = 0; i < _metric.Length; i++)
        {
            _metric[i] = (_metric[i] < 0) ? Math.Abs(_metric[i]) : 0;
        }
    }

    //// Compute the simple expected damage a bomb would score
    //private static void GetBlastPotential()
    //{
    //    _metric = new int[_field.Length];
    //    for (int i = 0; i < _field.Length; i++)
    //    {
    //        _metric[i] = (_field[i] > 0) ? 1 : 0;
    //        List<int> neighbours = GetNeighbours(i);
    //        if (neighbours != null)
    //        {
    //            foreach (int j in neighbours) _metric[i] += (_field[j] > 0) ? 1 : 0;
    //        }
    //    }            
    //}

    // Update the battle field upon dropping a bomb
    private static void Bomb(int cell)
    {
        List<int> neighbours = GetNeighbours(cell);
        foreach (int i in neighbours)
        {
            if (_field[i] > 0) _field[i]--;
        }
    }

    // Find the best bombing site - just return index of local maxima
    private static int FindBestBombingSite()
    {
        int max_idx = 0;
        int max_val = int.MinValue;
        for (int i = 0; i < _metric.Length; i++)
        {
            if (_metric[i] > max_val)
            {
                max_val = _metric[i];
                max_idx = i;
            }
        }
        return max_idx;
    }

    // Display the battle field on the console
    private static void PrintField(int cell)
    {
        for (int x = 0; x < _width; x++)
        {
            for (int y = 0; y < _length; y++)
            {
                int c = Get1DCoord(x, y);
                if (c == cell)
                    Console.Out.Write(string.Format("[{0}]", _field[c]).PadLeft(4));
                else
                    Console.Out.Write(string.Format(" {0} ", _field[c]).PadLeft(4));
            }
            Console.Out.Write(" || ");
            for (int y = 0; y < _length; y++)
            {
                int c = Get1DCoord(x, y);
                if (c == cell)
                    Console.Out.Write(string.Format("[{0}]", _metric[c]).PadLeft(4));
                else
                    Console.Out.Write(string.Format(" {0} ", _metric[c]).PadLeft(4));
            }
            Console.Out.WriteLine();
        }
        Console.Out.WriteLine();
    }           
  }
}

Mẫu ném bom kết quả là đầu ra như sau (giá trị trường ở bên trái, số liệu ở bên phải)

Round 1:
  2   1   4   2   3   2   6  ||   7  16   8  10   4  18   6
  3   5   3   1   1   1   9  ||  11  18  18  21  17  28   5
  4  [2]  4   2   3   4   1  ||  19 [32] 21  20  17  24  22
  7   6   2   4   4   3   6  ||   8  17  20  14  16  22   8
  1   2   1   1   1   2   4  ||  14  15  14  11  13  16   7

Round 2:
  2   1   4   2   3   2   6  ||   5  13   6   9   4  18   6
  2   4   2   1   1  [1]  9  ||  10  15  17  19  17 [28]  5
  3   2   3   2   3   4   1  ||  16  24  18  17  17  24  22
  6   5   1   4   4   3   6  ||   7  14  19  12  16  22   8
  1   2   1   1   1   2   4  ||  12  12  12  10  13  16   7

Round 3:
  2   1   4   2   2   1   5  ||   5  13   6   7   3  15   5
  2   4   2   1   0   1   8  ||  10  15  17  16  14  20   2
  3  [2]  3   2   2   3   0  ||  16 [24] 18  15  16  21  21
  6   5   1   4   4   3   6  ||   7  14  19  11  14  19   6
  1   2   1   1   1   2   4  ||  12  12  12  10  13  16   7

Round 4:
  2   1   4   2   2   1   5  ||   3  10   4   6   3  15   5
  1   3   1   1   0   1   8  ||   9  12  16  14  14  20   2
  2   2   2   2   2  [3]  0  ||  13  16  15  12  16 [21] 21
  5   4   0   4   4   3   6  ||   6  11  18   9  14  19   6
  1   2   1   1   1   2   4  ||  10   9  10   9  13  16   7

Round 5:
  2   1   4   2   2   1   5  ||   3  10   4   6   2  13   3
  1   3   1   1   0  [0]  7  ||   9  12  16  13  12 [19]  2
  2   2   2   2   1   3   0  ||  13  16  15  10  14  15  17
  5   4   0   4   3   2   5  ||   6  11  18   7  13  17   6
  1   2   1   1   1   2   4  ||  10   9  10   8  11  13   5

Round 6:
  2   1   4   2   1   0   4  ||   3  10   4   5   2  11   2
  1   3   1   1   0   0   6  ||   9  12  16  11   8  13   0
  2   2   2   2   0   2   0  ||  13  16  15   9  14  14  15
  5   4  [0]  4   3   2   5  ||   6  11 [18]  6  11  15   5
  1   2   1   1   1   2   4  ||  10   9  10   8  11  13   5

Round 7:
  2   1   4   2   1   0   4  ||   3  10   4   5   2  11   2
  1   3   1   1   0   0   6  ||   8  10  13   9   7  13   0
  2  [1]  1   1   0   2   0  ||  11 [15] 12   8  12  14  15
  5   3   0   3   3   2   5  ||   3   8  10   3   8  15   5
  1   1   0   0   1   2   4  ||   8   8   7   7   9  13   5

Round 8:
  2   1   4   2   1   0   4  ||   1   7   2   4   2  11   2
  0   2   0   1   0   0   6  ||   7   7  12   7   7  13   0
  1   1   0   1   0   2   0  ||   8   8  10   6  12  14  15
  4   2   0   3   3  [2]  5  ||   2   6   8   2   8 [15]  5
  1   1   0   0   1   2   4  ||   6   6   6   7   9  13   5

Round 9:
  2   1   4   2   1   0   4  ||   1   7   2   4   2  11   2
  0   2   0   1   0   0   6  ||   7   7  12   7   6  12   0
  1   1   0   1   0  [1]  0  ||   8   8  10   5  10 [13] 13
  4   2   0   3   2   2   4  ||   2   6   8   0   6   9   3
  1   1   0   0   0   1   3  ||   6   6   6   5   8  10   4

Round 10:
  2   1   4   2   1   0   4  ||   1   7   2   4   2  10   1
  0   2  [0]  1   0   0   5  ||   7   7 [12]  7   6  11   0
  1   1   0   1   0   1   0  ||   8   8  10   4   8   9  10
  4   2   0   3   1   1   3  ||   2   6   8   0   6   8   3
  1   1   0   0   0   1   3  ||   6   6   6   4   6   7   2

Round 11:
  2   0   3   1   1   0   4  ||   0   6   0   3   0  10   1
  0   1   0   0   0  [0]  5  ||   4   5   5   5   3 [11]  0
  1   0   0   0   0   1   0  ||   6   8   6   4   6   9  10
  4   2   0   3   1   1   3  ||   1   5   6   0   5   8   3
  1   1   0   0   0   1   3  ||   6   6   6   4   6   7   2

Round 12:
  2   0   3   1   0   0   3  ||   0   6   0   2   1   7   1
  0   1   0   0   0   0   4  ||   4   5   5   4   1   7   0
  1   0   0   0   0  [0]  0  ||   6   8   6   4   5  [9]  8
  4   2   0   3   1   1   3  ||   1   5   6   0   4   7   2
  1   1   0   0   0   1   3  ||   6   6   6   4   6   7   2

Round 13:
  2   0   3   1   0   0   3  ||   0   6   0   2   1   6   0
  0   1   0   0   0   0   3  ||   4   5   5   4   1   6   0
  1  [0]  0   0   0   0   0  ||   6  [8]  6   3   3   5   5
  4   2   0   3   0   0   2  ||   1   5   6   0   4   6   2
  1   1   0   0   0   1   3  ||   6   6   6   3   4   4   0

Round 14:
  2   0   3   1   0  [0]  3  ||   0   5   0   2   1  [6]  0
  0   0   0   0   0   0   3  ||   2   5   4   4   1   6   0
  0   0   0   0   0   0   0  ||   4   4   4   3   3   5   5
  3   1   0   3   0   0   2  ||   0   4   5   0   4   6   2
  1   1   0   0   0   1   3  ||   4   4   5   3   4   4   0

Round 15:
  2   0   3   1   0   0   2  ||   0   5   0   2   1   4   0
  0   0   0   0   0   0   2  ||   2   5   4   4   1   4   0
  0   0   0   0   0   0   0  ||   4   4   4   3   3   4   4
  3   1   0   3   0  [0]  2  ||   0   4   5   0   4  [6]  2
  1   1   0   0   0   1   3  ||   4   4   5   3   4   4   0

Round 16:
  2  [0]  3   1   0   0   2  ||   0  [5]  0   2   1   4   0
  0   0   0   0   0   0   2  ||   2   5   4   4   1   4   0
  0   0   0   0   0   0   0  ||   4   4   4   3   3   3   3
  3   1   0   3   0   0   1  ||   0   4   5   0   3   3   1
  1   1   0   0   0   0   2  ||   4   4   5   3   3   3   0

Round 17:
  1   0   2   1   0   0   2  ||   0   3   0   1   1   4   0
  0   0   0   0   0   0   2  ||   1   3   3   3   1   4   0
  0   0   0   0   0   0   0  ||   4   4   4   3   3   3   3
  3   1  [0]  3   0   0   1  ||   0   4  [5]  0   3   3   1
  1   1   0   0   0   0   2  ||   4   4   5   3   3   3   0

Round 18:
  1   0   2   1   0   0   2  ||   0   3   0   1   1   4   0
  0   0   0   0   0   0   2  ||   1   3   3   3   1   4   0
  0   0   0   0   0   0   0  ||   3   3   2   2   2   3   3
  3  [0]  0   2   0   0   1  ||   0  [4]  2   0   2   3   1
  1   0   0   0   0   0   2  ||   2   4   2   2   2   3   0

Round 19:
  1   0   2   1   0  [0]  2  ||   0   3   0   1   1  [4]  0
  0   0   0   0   0   0   2  ||   1   3   3   3   1   4   0
  0   0   0   0   0   0   0  ||   2   2   2   2   2   3   3
  2   0   0   2   0   0   1  ||   0   2   2   0   2   3   1
  0   0   0   0   0   0   2  ||   2   2   2   2   2   3   0

Round 20:
  1  [0]  2   1   0   0   1  ||   0  [3]  0   1   1   2   0
  0   0   0   0   0   0   1  ||   1   3   3   3   1   2   0
  0   0   0   0   0   0   0  ||   2   2   2   2   2   2   2
  2   0   0   2   0   0   1  ||   0   2   2   0   2   3   1
  0   0   0   0   0   0   2  ||   2   2   2   2   2   3   0

Round 21:
  0   0   1   1   0   0   1  ||   0   1   0   0   1   2   0
  0   0   0   0   0   0   1  ||   0   1   2   2   1   2   0
  0   0   0   0   0   0   0  ||   2   2   2   2   2   2   2
  2   0   0   2   0  [0]  1  ||   0   2   2   0   2  [3]  1
  0   0   0   0   0   0   2  ||   2   2   2   2   2   3   0

Round 22:
  0   0   1   1   0   0   1  ||   0   1   0   0   1   2   0
  0   0   0   0   0   0   1  ||   0   1   2   2   1   2   0
 [0]  0   0   0   0   0   0  ||  [2]  2   2   2   2   1   1
  2   0   0   2   0   0   0  ||   0   2   2   0   2   1   1
  0   0   0   0   0   0   1  ||   2   2   2   2   2   1   0

Round 23:
  0   0   1   1   0   0   1  ||   0   1   0   0   1   2   0
  0   0  [0]  0   0   0   1  ||   0   1  [2]  2   1   2   0
  0   0   0   0   0   0   0  ||   1   1   2   2   2   1   1
  1   0   0   2   0   0   0  ||   0   1   2   0   2   1   1
  0   0   0   0   0   0   1  ||   1   1   2   2   2   1   0

Round 24:
  0   0   0   0   0   0   1  ||   0   0   0   0   0   2   0
  0   0   0   0   0   0   1  ||   0   0   0   0   0   2   0
  0   0  [0]  0   0   0   0  ||   1   1  [2]  2   2   1   1
  1   0   0   2   0   0   0  ||   0   1   2   0   2   1   1
  0   0   0   0   0   0   1  ||   1   1   2   2   2   1   0

Round 25:
  0   0   0   0   0  [0]  1  ||   0   0   0   0   0  [2]  0
  0   0   0   0   0   0   1  ||   0   0   0   0   0   2   0
  0   0   0   0   0   0   0  ||   1   1   1   1   1   1   1
  1   0   0   1   0   0   0  ||   0   1   1   0   1   1   1
  0   0   0   0   0   0   1  ||   1   1   1   1   1   1   0

Round 26:
  0   0   0   0   0   0   0  ||   0   0   0   0   0   0   0
  0   0   0   0   0   0   0  ||   0   0   0   0   0   0   0
 [0]  0   0   0   0   0   0  ||  [1]  1   1   1   1   0   0
  1   0   0   1   0   0   0  ||   0   1   1   0   1   1   1
  0   0   0   0   0   0   1  ||   1   1   1   1   1   1   0

Round 27:
  0   0   0   0   0   0   0  ||   0   0   0   0   0   0   0
  0   0   0   0   0   0   0  ||   0   0   0   0   0   0   0
  0   0  [0]  0   0   0   0  ||   0   0  [1]  1   1   0   0
  0   0   0   1   0   0   0  ||   0   0   1   0   1   1   1
  0   0   0   0   0   0   1  ||   0   0   1   1   1   1   0

Round 28:
  0   0   0   0   0   0   0  ||   0   0   0   0   0   0   0
  0   0   0   0   0   0   0  ||   0   0   0   0   0   0   0
  0   0   0   0   0   0   0  ||   0   0   0   0   0   0   0
  0   0   0   0   0  [0]  0  ||   0   0   0   0   0  [1]  1
  0   0   0   0   0   0   1  ||   0   0   0   0   0   1   0

Done in 28 rounds

1

Điều này có thể được giải quyết bằng cách sử dụng cây có độ sâu O (3 ^ (n)). Trong đó n là tổng của tất cả các hình vuông.

Đầu tiên hãy xem xét việc giải quyết vấn đề với một cây O (9 ^ n) là chuyện nhỏ, chỉ cần xem xét tất cả các vị trí ném bom có ​​thể. Ví dụ, xem triển khai của Alfe .

Tiếp theo nhận ra rằng chúng ta có thể làm việc để ném bom từ dưới lên và vẫn có được mô hình ném bom tối thiểu.

  1. Bắt đầu từ góc dưới bên trái.
  2. Ném bom vào quên lãng với những vở kịch duy nhất có ý nghĩa (lên và sang phải).
  3. Di chuyển một hình vuông sang bên phải.
  4. Mặc dù mục tiêu có giá trị lớn hơn 0, hãy xem xét mỗi trong số 2 lần chơi có ý nghĩa (thẳng lên hoặc sang phải), giảm giá trị của mục tiêu xuống một và tạo một nhánh mới cho mỗi khả năng.
  5. Di chuyển cái khác sang phải.
  6. Mặc dù mục tiêu có giá trị lớn hơn 0, hãy xem xét từng trong số 3 lần chơi có ý nghĩa (lên trái, lên và lên phải), giảm giá trị của mục tiêu xuống một và tạo một nhánh mới cho mỗi khả năng.
  7. Lặp lại bước 5 và 6 cho đến khi hàng được loại bỏ.
  8. Di chuyển lên một hàng và lặp lại các bước từ 1 đến 7 cho đến khi câu đố được giải.

Thuật toán này đúng vì

  1. Nó là cần thiết để hoàn thành mỗi hàng tại một số điểm.
  2. Hoàn thành một hàng luôn đòi hỏi một vở kịch ở trên, một bên dưới hoặc trong hàng đó.
  3. Luôn luôn là tốt hoặc tốt hơn để chọn một phát trên hàng không rõ ràng thấp nhất so với chơi trên hàng hoặc dưới hàng.

Trong thực tế, thuật toán này sẽ thường xuyên làm tốt hơn mức tối đa về mặt lý thuyết của nó bởi vì nó sẽ thường xuyên ném bom hàng xóm và giảm quy mô tìm kiếm. Nếu chúng tôi giả định rằng mỗi lần ném bom làm giảm giá trị của 4 mục tiêu bổ sung, thì thuật toán của chúng tôi sẽ chạy trong O (3 ^ (n / 4)) hoặc khoảng O (1.3 ^ n).

Bởi vì thuật toán này vẫn còn theo cấp số nhân, nên sẽ hạn chế độ sâu của tìm kiếm. Chúng ta có thể giới hạn số nhánh được phép ở một số X, và một khi chúng ta sâu đến mức này, chúng ta buộc thuật toán chọn đường dẫn tốt nhất mà nó đã xác định cho đến nay (nhánh có tổng số bảng tối thiểu trong một trong các lá cuối của nó ). Sau đó, thuật toán của chúng tôi được đảm bảo chạy trong thời gian O (3 ^ X), nhưng nó không được đảm bảo để có câu trả lời chính xác. Tuy nhiên, chúng ta luôn có thể tăng X và kiểm tra theo kinh nghiệm nếu đánh đổi giữa tính toán tăng và câu trả lời tốt hơn là đáng giá.


1

hàm đánh giá, tổng cộng:

int f (int ** matrix, int width, int height, int x, int y)
{
    int m[3][3] = { 0 };

    m[1][1] = matrix[x][y];
    if (x > 0) m[0][1] = matrix[x-1][y];
    if (x < width-1) m[2][1] = matrix[x+1][y];

    if (y > 0)
    {
        m[1][0] = matrix[x][y-1];
        if (x > 0) m[0][0] = matrix[x-1][y-1];
        if (x < width-1) m[2][0] = matrix[x+1][y-1];
    }

    if (y < height-1)
    {
        m[1][2] = matrix[x][y+1];
        if (x > 0) m[0][2] = matrix[x-1][y+1];
        if (x < width-1) m[2][2] = matrix[x+1][y+1];
    }

    return m[0][0]+m[0][1]+m[0][2]+m[1][0]+m[1][1]+m[1][2]+m[2][0]+m[2][1]+m[2][2];
}

hàm mục tiêu:

Point bestState (int ** matrix, int width, int height)
{
    Point p = new Point(0,0);
    int bestScore = 0;
    int b = 0;

    for (int i=0; i<width; i++)
        for (int j=0; j<height; j++)
        {
            b = f(matrix,width,height,i,j);

            if (b > bestScore)
            {
                bestScore = best;
                p = new Point(i,j);
            }
        }

    retunr p;
}

chức năng phá hủy:

void destroy (int ** matrix, int width, int height, Point p)
{
    int x = p.x;
    int y = p.y;

    if(matrix[x][y] > 0) matrix[x][y]--;
    if (x > 0) if(matrix[x-1][y] > 0) matrix[x-1][y]--;
    if (x < width-1) if(matrix[x+1][y] > 0) matrix[x+1][y]--;

    if (y > 0)
    {
        if(matrix[x][y-1] > 0) matrix[x][y-1]--;
        if (x > 0) if(matrix[x-1][y-1] > 0) matrix[x-1][y-1]--;
        if (x < width-1) if(matrix[x+1][y-1] > 0) matrix[x+1][y-1]--;
    }

    if (y < height-1)
    {
        if(matrix[x][y] > 0) matrix[x][y+1]--;
        if (x > 0) if(matrix[x-1][y+1] > 0) matrix[x-1][y+1]--;
        if (x < width-1) if(matrix[x+1][y+1] > 0) matrix[x+1][y+1]--;
    }
}

chức năng mục tiêu:

bool isGoal (int ** matrix, int width, int height)
{
    for (int i=0; i<width; i++)
        for (int j=0; j<height; j++)
            if (matrix[i][j] > 0)
                return false;
    return true;
}

chức năng tối đa hóa tuyến tính:

void solve (int ** matrix, int width, int height)
{
    while (!isGoal(matrix,width,height))
    {
        destroy(matrix,width,height, bestState(matrix,width,height));
    }
}

Điều này không tối ưu, nhưng có thể được tối ưu hóa thông qua việc tìm kiếm một chức năng đánh giá tốt hơn ..

.. nhưng nghĩ về vấn đề này, tôi đã nghĩ rằng một trong những vấn đề chính là bị bỏ rơi các con số ở giữa các số 0 tại một số điểm, vì vậy tôi sẽ thực hiện một cách tiếp cận khác .. đó là thống trị các giá trị tối thiểu thành 0, sau đó thử thoát các số 0 càng tốt, dẫn đến giảm thiểu chung các giá trị hiện có tối thiểu hoặc hơn


0

Tất cả vấn đề này sôi nổi là tính toán khoảng cách chỉnh sửa. Đơn giản chỉ cần tính toán một biến thể của khoảng cách Levenshtein giữa ma trận đã cho và ma trận không, trong đó các chỉnh sửa được thay thế bằng các quả bom, sử dụng lập trình động để lưu trữ khoảng cách giữa các mảng trung gian. Tôi đề nghị sử dụng hàm băm của ma trận làm khóa. Trong giả Python:

memo = {}

def bomb(matrix,i,j):
    # bomb matrix at i,j

def bombsRequired(matrix,i,j):
    # bombs required to zero matrix[i,j]

def distance(m1, i, len1, m2, j, len2):
    key = hash(m1)
    if memo[key] != None: 
        return memo[key]

    if len1 == 0: return len2
    if len2 == 0: return len1

    cost = 0
    if m1 != m2: cost = m1[i,j]
    m = bomb(m1,i,j)
    dist = distance(str1,i+1,len1-1,str2,j+1,len2-1)+cost)
    memo[key] = dist
    return dist

0

Đây là một câu trả lời cho câu hỏi đầu tiên. Tôi đã không nhận thấy rằng anh ấy đã thay đổi các thông số.

Tạo một danh sách tất cả các mục tiêu. Gán một giá trị cho mục tiêu dựa trên số lượng giá trị dương bị ảnh hưởng bởi sự sụt giảm (chính nó và tất cả các hàng xóm). Giá trị cao nhất sẽ là một chín.

Sắp xếp các mục tiêu theo số lượng mục tiêu bị tác động (Giảm dần), với sắp xếp giảm dần thứ cấp trên tổng của từng mục tiêu bị ảnh hưởng.

Thả một quả bom vào mục tiêu được xếp hạng cao nhất, sau đó tính lại mục tiêu và lặp lại cho đến khi tất cả các giá trị mục tiêu bằng không.

Đồng ý, điều này không phải lúc nào cũng là tối ưu nhất. Ví dụ,

100011
011100
011100
011100
000000
100011

Cách tiếp cận này sẽ mất 5 quả bom để xóa. Mặc dù vậy, tối ưu, bạn có thể làm điều đó trong 4. Tuy nhiên, khá gần và không có quay lại. Đối với hầu hết các tình huống, nó sẽ là tối ưu, hoặc rất gần.

Sử dụng các số vấn đề ban đầu, phương pháp này giải quyết trong 28 quả bom.

Thêm mã để thể hiện phương pháp này (sử dụng biểu mẫu có nút):

         private void button1_Click(object sender, EventArgs e)
    {
        int[,] matrix = new int[10, 10] {{5, 20, 7, 1, 9, 8, 19, 16, 11, 3}, 
                                         {17, 8, 15, 17, 12, 4, 5, 16, 8, 18},
                                         { 4, 19, 12, 11, 9, 7, 4, 15, 14, 6},
                                         { 17, 20, 4, 9, 19, 8, 17, 2, 10, 8},
                                         { 3, 9, 10, 13, 8, 9, 12, 12, 6, 18}, 
                                         {16, 16, 2, 10, 7, 12, 17, 11, 4, 15},
                                         { 11, 1, 15, 1, 5, 11, 3, 12, 8, 3},
                                         { 7, 11, 16, 19, 17, 11, 20, 2, 5, 19},
                                         { 5, 18, 2, 17, 7, 14, 19, 11, 1, 6},
                                         { 13, 20, 8, 4, 15, 10, 19, 5, 11, 12}};


        int value = 0;
        List<Target> Targets = GetTargets(matrix);
        while (Targets.Count > 0)
        {
            BombTarget(ref matrix, Targets[0]);
            value += 1;
            Targets = GetTargets(matrix);
        }
        Console.WriteLine( value);
        MessageBox.Show("done: " + value);
    }

    private static void BombTarget(ref int[,] matrix, Target t)
    {
        for (int a = t.x - 1; a <= t.x + 1; a++)
        {
            for (int b = t.y - 1; b <= t.y + 1; b++)
            {
                if (a >= 0 && a <= matrix.GetUpperBound(0))
                {
                    if (b >= 0 && b <= matrix.GetUpperBound(1))
                    {
                        if (matrix[a, b] > 0)
                        {
                            matrix[a, b] -= 1;
                        }
                    }
                }
            }
        }
        Console.WriteLine("Dropped bomb on " + t.x + "," + t.y);
    }

    private static List<Target> GetTargets(int[,] matrix)
    {
        List<Target> Targets = new List<Target>();
        int width = matrix.GetUpperBound(0);
        int height = matrix.GetUpperBound(1);
        for (int x = 0; x <= width; x++)
        {
            for (int y = 0; y <= height; y++)
            {
                Target t = new Target();
                t.x = x;
                t.y = y;
                SetTargetValue(matrix, ref t);
                if (t.value > 0) Targets.Add(t);
            }
        }
        Targets = Targets.OrderByDescending(x => x.value).ThenByDescending( x => x.sum).ToList();
        return Targets;
    }

    private static void SetTargetValue(int[,] matrix, ref Target t)
    {
        for (int a = t.x - 1; a <= t.x + 1; a++)
        {
            for (int b = t.y - 1; b <= t.y + 1; b++)
            {
                if (a >= 0 && a <= matrix.GetUpperBound(0))
                {
                    if (b >= 0 && b <= matrix.GetUpperBound(1))
                    {
                        if (matrix[ a, b] > 0)
                        {
                            t.value += 1;
                            t.sum += matrix[a,b];
                        }

                    }
                }
            }
        }

    }

Một lớp học bạn sẽ cần:

        class Target
    {
        public int value;
        public int sum;
        public int x;
        public int y;
    }

1
Không tối ưu. Counterexample: 09090Cách tiếp cận này cần 18 quả bom. Nó có thể được thực hiện trong 9.
Mystical

@Mysticial Bạn đã không đọc kỹ câu trả lời. Vì nó dựa trên số lượng các trường khác không bị ảnh hưởng, thuật toán này sẽ đánh bom số 0 ở giữa và sẽ được thực hiện trong 9 giọt. Giá trị cao của 9 là bởi vì có tới tám hàng xóm và chính nó.
Anthony Queen

Sau đó, làm thế nào về 1010101, 0010100?
Bí ẩn

@Mysticial Đối với người đầu tiên, số 0 đầu tiên, số 0 cuối cùng sẽ được nhấn. Nó sẽ là hai giọt. Đó là bởi vì mỗi lần nó thả một quả bom, nó sẽ tính toán lại mục tiêu tốt nhất tiếp theo. Đối với ví dụ cuối cùng, số 0 ở giữa một lần nữa; Một giọt.
Nữ hoàng Anthony

1
@AnthonyQueen: điều này không hoạt động. vui lòng xem chat.stackoverflow.com/transcript/message/8224273#8224273 cho ví dụ của tôi.
nneonneo
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.