Xây dựng Trình tối ưu hóa cường độ phi hình ảnh ™


12

Nonogram là một trò chơi giải đố của Nhật Bản, trong đó mục tiêu là vẽ một bức tranh đen trắng theo một danh sách các vùng tiếp giáp nhau, như vậy:

Một ví dụ phi hình với một "lambda".

Xác định cường độ phi hình học của một hàng hoặc cột là số vùng màu đen liền kề trong hàng hoặc cột đó. Ví dụ, hàng trên cùng có cường độ phi hình học là 1, vì có một vùng gồm 2 hình vuông trong hàng đó. Hàng thứ 8 có cường độ phi hình học là 3 vì nó có 2, 2, 1.

Một hàng hoặc cột trống có cường độ phi hình học là 0.


Nhiệm vụ của bạn là viết một chương trình lấy lưới giải pháp cho một biểu đồ không và tạo ra một lưới giải pháp với càng ít hình vuông điền vào càng tốt trong đó mỗi hàng và cột có cùng một biểu đồ phi hình học như lưới giải pháp đã cho.

Ví dụ: lưới phi hình với tất cả các ô vuông được điền vào có cường độ phi hình học là 1 trên mỗi hàng hoặc cột:

Một nonogram 10x10 trong đó mỗi hình vuông được điền vào.

Độ lớn phi hình học tương tự có thể đạt được chỉ bằng cách có một đường chéo xuyên qua lưới, làm giảm đáng kể số lượng hình vuông điền vào:

Một phi hình 10 x 10 với độ lớn phi hình học như trên.


Chương trình của bạn sẽ nhận được một đầu vào bao gồm 50.000 dòng từ tệp này ( tệp văn bản tar.gz 1,32 MB; giải nén 2,15 MB), mỗi dòng đại diện cho một lưới giải pháp phi hình 16 × 16 với các ô vuông được điền ngẫu nhiên (80% màu đen) và xuất ra 50.000 dòng khác, mỗi dòng chứa lưới giải pháp được tối ưu hóa cho lưới đầu vào tương ứng.

Mỗi lưới được biểu diễn dưới dạng một chuỗi base64 với 43 ký tự (mã hóa hình vuông từ trái sang phải, sau đó từ trên xuống dưới) và chương trình của bạn sẽ cần trả về đầu ra của nó theo cùng định dạng. Ví dụ: lưới đầu tiên trong tệp là E/lu/+7/f/3rp//f799xn/9//2mv//nvj/bt/yc9/40=và hiển thị dưới dạng:

ví dụ đầu tiên

Lưới bắt đầu với Eánh xạ tới 000100, vì vậy sáu ô đầu tiên ở hàng trên cùng đều có màu trắng ngoại trừ ô thứ tư. Ký tự tiếp theo là /ánh xạ tới 111111, vì vậy 6 ô tiếp theo đều có màu đen - v.v.


Chương trình của bạn thực sự phải trả về một lưới giải pháp với cường độ không chính xác cho mỗi trong số 50.000 trường hợp thử nghiệm. Nó được phép trả lại cùng một lưới với đầu vào nếu không tìm thấy gì tốt hơn.

Chương trình trả về tổng số ô vuông điền ít nhất (bằng bất kỳ ngôn ngữ nào) là người chiến thắng, với mã ngắn hơn là bộ bẻ khóa.


Bảng điểm hiện tại:

  1. 3.637.260 - Điếc, Java
  2. 7.270.894 - flawr, Matlab
  3. 10,239,288 - Joe Z., Bash

1
Tôi không thực sự thấy quan điểm của mã hóa cơ sở 64 và làm việc với đó là một nỗi đau thực sự. Sẽ không dễ dàng hơn nếu chỉ tạo ra các dòng và số không? Hoặc mã hóa toàn bộ điều dưới dạng bitmap?
flawr

@flawr: Nó làm giảm kích thước tệp, chủ yếu (theo hệ số 6 so với chỉ 1 và 0). Ngoài ra, bitmap sẽ còn khó hơn để làm việc với.
Joe Z.

Bạn chỉ có thể tạo một hình ảnh đen trắng, dễ đọc / ghi và cùng kích thước với mã hóa b64.
flawr

2
cũng không phải là một fan hâm mộ của mã hóa b64, cho đầu vào và / hoặc đầu ra. Tại sao không để i / o ở bất kỳ định dạng thuận tiện nào?
Sparr

1
Giả sử là vậy, tôi nên có một giải pháp tối ưu vào thời điểm này vào ngày mai.
quintopia

Câu trả lời:


7

Python 2 & PuLP - 2.644.688 ô vuông (tối thiểu hóa tối ưu); 10,753,553 hình vuông (tối đa hóa tối đa)

Tối thiểu được đánh gôn tới 1152 byte

from pulp import*
x=0
f=open("c","r")
g=open("s","w")
for k,m in enumerate(f):
 if k%2:
    b=map(int,m.split())
    p=LpProblem("Nn",LpMinimize)
    q=map(str,range(18))
    ir=q[1:18]
    e=LpVariable.dicts("c",(q,q),0,1,LpInteger)
    rs=LpVariable.dicts("rs",(ir,ir),0,1,LpInteger)
    cs=LpVariable.dicts("cs",(ir,ir),0,1,LpInteger)
    p+=sum(e[r][c] for r in q for c in q),""
    for i in q:p+=e["0"][i]==0,"";p+=e[i]["0"]==0,"";p+=e["17"][i]==0,"";p+=e[i]["17"]==0,""
    for o in range(289):i=o/17+1;j=o%17+1;si=str(i);sj=str(j);l=e[si][str(j-1)];ls=rs[si][sj];p+=e[si][sj]<=l+ls,"";p+=e[si][sj]>=l-ls,"";p+=e[si][sj]>=ls-l,"";p+=e[si][sj]<=2-ls-l,"";l=e[str(i-1)][sj];ls=cs[si][sj];p+=e[si][sj]<=l+ls,"";p+=e[si][sj]>=l-ls,"";p+=e[si][sj]>=ls-l,"";p+=e[si][sj]<=2-ls-l,""
    for r,z in enumerate(a):p+=lpSum([rs[str(r+1)][c] for c in ir])==2*z,""
    for c,z in enumerate(b):p+=lpSum([cs[r][str(c+1)] for r in ir])==2*z,""
    p.solve()
    for r in ir:
     for c in ir:g.write(str(int(e[r][c].value()))+" ")
     g.write('\n')
    g.write('%d:%d\n\n'%(-~k/2,value(p.objective)))
    x+=value(p.objective)
 else:a=map(int,m.split())
print x

(NB: các dòng thụt nhiều bắt đầu bằng các tab, không phải dấu cách.)

Kết quả ví dụ: https://drive.google.com/file/d/0B-0NVE9E8UJiX3IyQkJZVk82Vkk/view?usp=shaming

Hóa ra các vấn đề như có thể chuyển đổi dễ dàng sang Chương trình tuyến tính Integer và tôi cần một vấn đề cơ bản để tìm hiểu cách sử dụng giao diện python của PuLP cho một loạt các trình giải LP cho một dự án của riêng tôi. Nó cũng chỉ ra rằng PuLP cực kỳ dễ sử dụng và trình xây dựng LP không được phép hoạt động hoàn hảo ngay lần đầu tiên tôi dùng thử.

Hai điều tốt đẹp về việc sử dụng một bộ giải IP liên kết và chi nhánh để thực hiện công việc khó khăn này để giải quyết vấn đề này cho tôi (ngoài việc không phải thực hiện một chi nhánh và bộ giải ràng buộc) là

  • Người giải quyết mục đích xây dựng là thực sự nhanh chóng. Chương trình này giải quyết tất cả 50000 vấn đề trong khoảng 17 giờ trên PC tại nhà tương đối thấp của tôi. Mỗi trường hợp mất từ ​​1-1,5 giây để giải quyết.
  • Họ sản xuất các giải pháp tối ưu được đảm bảo (hoặc nói với bạn rằng họ đã không làm như vậy). Vì vậy, tôi có thể tự tin rằng sẽ không có ai đánh bại điểm số của tôi trong các ô vuông (mặc dù ai đó có thể buộc nó và đánh bại tôi trong phần chơi gôn).

Cách sử dụng chương trình này

Trước tiên, bạn sẽ cần cài đặt PuLP. pip install pulpnên thực hiện các mẹo nếu bạn đã cài đặt pip.

Sau đó, bạn sẽ cần đặt đoạn mã sau vào một tệp có tên "c": https://drive.google.com/file/d/0B-0NVE9E8UJiNFdmYlk1aV9aYzQ/view?usp=shaming

Sau đó, chạy chương trình này trong bất kỳ bản dựng Python 2 muộn nào từ cùng một thư mục. Trong vòng chưa đầy một ngày, bạn sẽ có một tệp có tên "s" chứa 50.000 lưới không chữ được giải (ở định dạng có thể đọc được), mỗi tệp có tổng số ô vuông được liệt kê bên dưới.

Thay vào đó, nếu bạn muốn tối đa hóa số lượng hình vuông đầy, thay đổi LpMinimizedòng 8 thành LpMaximizethay thế. Bạn sẽ nhận được đầu ra rất giống như thế này: https://drive.google.com/file/d/0B-0NVE9E8UJiYjJ2bzlvZ0RXcUU/view?usp=shaming

định dạng đầu vào

Chương trình này sử dụng định dạng đầu vào được sửa đổi, vì Joe Z. nói rằng chúng tôi sẽ được phép mã hóa lại định dạng đầu vào nếu chúng tôi muốn trong một nhận xét về OP. Nhấp vào liên kết ở trên để xem nó trông như thế nào. Nó bao gồm 10000 dòng, mỗi dòng chứa 16 số. Các dòng được đánh số chẵn là độ lớn cho các hàng của một thể hiện nhất định, trong khi các dòng được đánh số lẻ là độ lớn cho các cột của cùng thể hiện với dòng trên chúng. Tập tin này được tạo bởi chương trình sau:

from bitqueue import *

with open("nonograms_b64.txt","r") as f:
    with open("nonogram_clues.txt","w") as g:
        for line in f:
            q = BitQueue(line.decode('base64'))
            nonogram = []
            for i in range(256):
                if not i%16: row = []
                row.append(q.nextBit())
                if not -~i%16: nonogram.append(row)
            s=""
            for row in nonogram:
                blocks=0                         #magnitude counter
                for i in range(16):
                    if row[i]==1 and (i==0 or row[i-1]==0): blocks+=1
                s+=str(blocks)+" "
            print >>g, s
            nonogram = map(list, zip(*nonogram)) #transpose the array to make columns rows
            s=""
            for row in nonogram:
                blocks=0
                for i in range(16):
                    if row[i]==1 and (i==0 or row[i-1]==0): blocks+=1
                s+=str(blocks)+" "
            print >>g, s

(Chương trình mã hóa lại này cũng cho tôi thêm cơ hội để kiểm tra lớp BitQueue tùy chỉnh mà tôi đã tạo cho cùng một dự án được đề cập ở trên. Nó chỉ đơn giản là một hàng đợi để dữ liệu có thể được đẩy thành chuỗi bit HOẶC byte và từ đó dữ liệu có thể được bật lên một bit hoặc một byte tại một thời điểm. Trong trường hợp này, nó hoạt động hoàn hảo.)

Tôi đã mã hóa lại đầu vào với lý do cụ thể là để xây dựng ILP, thông tin bổ sung về các lưới được sử dụng để tạo ra cường độ là hoàn toàn vô dụng. Độ lớn là những hạn chế duy nhất, và vì vậy độ lớn là tất cả những gì tôi cần truy cập.

Người xây dựng ILP bị phản đối

from pulp import *
total = 0
with open("nonogram_clues.txt","r") as f:
    with open("solutions.txt","w") as g:
        for k,line in enumerate(f):
            if k%2:
                colclues=map(int,line.split())
                prob = LpProblem("Nonogram",LpMinimize)
                seq = map(str,range(18))
                rows = seq
                cols = seq
                irows = seq[1:18]
                icols = seq[1:18]
                cells = LpVariable.dicts("cell",(rows,cols),0,1,LpInteger)
                rowseps = LpVariable.dicts("rowsep",(irows,icols),0,1,LpInteger)
                colseps = LpVariable.dicts("colsep",(irows,icols),0,1,LpInteger)
                prob += sum(cells[r][c] for r in rows for c in cols),""
                for i in rows:
                    prob += cells["0"][i] == 0,""
                    prob += cells[i]["0"] == 0,""
                    prob += cells["17"][i] == 0,""
                    prob += cells[i]["17"] == 0,""
                for i in range(1,18):
                    for j in range(1,18):
                        si = str(i); sj = str(j)
                        l = cells[si][str(j-1)]; ls = rowseps[si][sj]
                        prob += cells[si][sj] <= l + ls,""
                        prob += cells[si][sj] >= l - ls,""
                        prob += cells[si][sj] >= ls - l,""
                        prob += cells[si][sj] <= 2 - ls - l,""
                        l = cells[str(i-1)][sj]; ls = colseps[si][sj]
                        prob += cells[si][sj] <= l + ls,""
                        prob += cells[si][sj] >= l - ls,""
                        prob += cells[si][sj] >= ls - l,""
                        prob += cells[si][sj] <= 2 - ls - l,""
                for r,clue in enumerate(rowclues):
                    prob += lpSum([rowseps[str(r+1)][c] for c in icols]) == 2 * clue,""
                for c,clue in enumerate(colclues):
                    prob += lpSum([colseps[r][str(c+1)] for r in irows]) == 2 * clue,""
                prob.solve()
                print "Status for problem %d: "%(-~k/2),LpStatus[prob.status]
                for r in rows[1:18]:
                    for c in cols[1:18]:
                        g.write(str(int(cells[r][c].value()))+" ")
                    g.write('\n')
                g.write('Filled squares for %d: %d\n\n'%(-~k/2,value(prob.objective)))
                total += value(prob.objective)
            else:
                rowclues=map(int,line.split())
print "Total number of filled squares: %d"%total

Đây là chương trình thực sự tạo ra "ví dụ đầu ra" được liên kết ở trên. Do đó, các chuỗi dài thêm ở cuối mỗi lưới, mà tôi đã cắt bớt khi chơi golf. (Phiên bản được đánh gôn sẽ tạo ra đầu ra giống hệt nhau, trừ các từ "Filled squares for ")

Làm thế nào nó hoạt động

cells = LpVariable.dicts("cell",(rows,cols),0,1,LpInteger)
rowseps = LpVariable.dicts("rowsep",(irows,icols),0,1,LpInteger)
colseps = LpVariable.dicts("colsep",(irows,icols),0,1,LpInteger)

Tôi sử dụng lưới 18x18, với phần 16x16 ở giữa là giải pháp câu đố thực tế. cellslà lưới này. Dòng đầu tiên tạo ra 324 biến nhị phân: "cell_0_0", "cell_0_1", v.v. Tôi cũng tạo các lưới của "khoảng trắng" giữa và xung quanh các ô trong phần giải pháp của lưới. rowsepschỉ ra 289 biến tượng trưng cho các không gian phân tách các ô theo chiều ngang, trong khi colsepstương tự chỉ đến các biến đánh dấu các không gian phân tách các ô theo chiều dọc. Đây là một sơ đồ unicode:

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 0 0 0 0 0 0 0 0 0 0 0 0 0 0

Các 0s và s là các giá trị nhị phân theo dõi bởi các cellbiến, |s là những giá trị nhị phân theo dõi bởi các rowsepbiến, và -s là các giá trị nhị phân theo dõi bởi các colsepbiến.

prob += sum(cells[r][c] for r in rows for c in cols),""

Đây là chức năng khách quan. Chỉ là tổng của tất cả các cellbiến. Vì đây là các biến nhị phân, đây chỉ chính xác là số lượng hình vuông đầy trong giải pháp.

for i in rows:
    prob += cells["0"][i] == 0,""
    prob += cells[i]["0"] == 0,""
    prob += cells["17"][i] == 0,""
    prob += cells[i]["17"] == 0,""

Điều này chỉ đặt các ô xung quanh mép ngoài của lưới thành 0 (đó là lý do tại sao tôi biểu diễn chúng dưới dạng số 0 ở trên). Đây là cách tốt nhất để theo dõi có bao nhiêu "khối" ô được điền, vì nó đảm bảo rằng mọi thay đổi từ không được điền sang được điền (di chuyển qua một cột hoặc hàng) được khớp với một thay đổi tương ứng từ được điền vào không được lấp đầy (và ngược lại ), ngay cả khi ô đầu tiên hoặc ô cuối cùng trong hàng được điền. Đây là lý do duy nhất để sử dụng lưới 18x18 ở vị trí đầu tiên. Đây không phải là cách duy nhất để đếm các khối, nhưng tôi nghĩ đó là cách đơn giản nhất.

for i in range(1,18):
    for j in range(1,18):
        si = str(i); sj = str(j)
        l = cells[si][str(j-1)]; ls = rowseps[si][sj]
        prob += cells[si][sj] <= l + ls,""
        prob += cells[si][sj] >= l - ls,""
        prob += cells[si][sj] >= ls - l,""
        prob += cells[si][sj] <= 2 - ls - l,""
        l = cells[str(i-1)][sj]; ls = colseps[si][sj]
        prob += cells[si][sj] <= l + ls,""
        prob += cells[si][sj] >= l - ls,""
        prob += cells[si][sj] >= ls - l,""
        prob += cells[si][sj] <= 2 - ls - l,""

Đây là thịt thực sự của logic của ILP. Về cơ bản, nó yêu cầu mỗi ô (trừ các ô trong hàng và cột đầu tiên) phải là xor logic của ô và dấu phân cách trực tiếp bên trái trong hàng của nó và ngay phía trên nó trong cột của nó. Tôi đã nhận được các ràng buộc mô phỏng một xor trong chương trình số nguyên {0,1} từ câu trả lời tuyệt vời này: /cs//a/12118/44289

Để giải thích thêm một chút: ràng buộc xor này làm cho nó để các dấu phân cách có thể là 1 khi và chỉ khi chúng nằm giữa các ô là 0 và 1 (đánh dấu một sự thay đổi từ không được điền thành hoặc ngược lại). Do đó, sẽ có chính xác gấp đôi số phân cách có giá trị 1 trong một hàng hoặc cột so với số khối trong hàng hoặc cột đó. Nói cách khác, tổng của các dấu phân cách trên một hàng hoặc cột đã cho chính xác gấp đôi độ lớn của hàng / cột đó. Do đó các ràng buộc sau:

for r,clue in enumerate(rowclues):
    prob += lpSum([rowseps[str(r+1)][c] for c in icols]) == 2 * clue,""
for c,clue in enumerate(colclues):
    prob += lpSum([colseps[r][str(c+1)] for r in irows]) == 2 * clue,""

Và đó là khá nhiều đó. Phần còn lại chỉ yêu cầu người giải mặc định giải ILP, sau đó định dạng giải pháp kết quả khi nó ghi vào tệp.


Câu trả lời thực sự tốt. Làm cho tôi muốn tìm hiểu về người giải quyết LP. Bạn có nghĩ rằng nó có thể được sử dụng để giải quyết lũ lụt câu đố (liên kết) cho một bảng 19x19, 6 màu (liên quan đến thời gian để tính toán một giải pháp) không? Tôi đã trả lời cuộc thi đó (và đã thắng nó) tuy nhiên phương pháp của tôi (thuật toán tìm kiếm A *) chỉ đưa ra các giải pháp không tối ưu.
tigrou

@tigrou Cảm ơn. Tôi không chắc vấn đề lũ là đủ tuyến tính để thừa nhận một giải pháp như vậy. Tôi chắc chắn không thể thấy làm thế nào để mô hình hóa nó theo cách này.
quintopia

Có vẻ như ai đó đã thử nó: kunigami.blog/2012/09/16/flood-it-an-exact-approach Tuy nhiên họ không thể giải pháp tối ưu trong thời gian khả thi cho một bảng 14x14.
tigrou

3

Java, 6.093.092 4.332.656 3.637.260 ô vuông (thu nhỏ), 10,567,550 10,567,691 10,568,746 ô vuông (tối đa hóa)

Cả hai biến thể của chương trình liên tục thực hiện các hoạt động trên lưới nguồn, mà không thay đổi cường độ.

Bộ thu nhỏ

co lại()

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

Nếu một hình vuông màu đen có 2 hàng xóm trắng và 2 hàng xóm màu đen ở góc 90 °, nó có thể được thay thế bằng hình vuông màu trắng.

di chuyển ()

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

Trong các cấu hình như trên đường màu đen có thể được di chuyển sang bên phải. Điều này được thực hiện lặp đi lặp lại cho cả 4 hướng theo chiều kim đồng hồ và ngược chiều kim đồng hồ, để mở ra những khả năng thu nhỏ mới.

Tối đa hóa

Bỏ ghi chú trong dòng main()và nhận xét dòng trên nó cho phiên bản này.

lớn lên()

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

Nếu một hình vuông màu trắng có 2 hàng xóm trắng và 2 hàng xóm màu đen ở góc 90 °, nó có thể được thay thế bằng hình vuông màu đen.

di chuyển ()

Tương tự như trong Minimizer.

Nguồn

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileReader;
import java.io.FileWriter;
import java.util.Arrays;
import java.util.Base64;
import java.util.function.Function;

public class Main {
    private static final int SIZE = 16;
    private static final int SIZE_4 = SIZE + 4;
    private static final int E = 0;
    private static final int N = 1;
    private static final int W = 2;
    private static final int S = 3;

    private static final Base64.Decoder decoder = Base64.getMimeDecoder();
    private static final Base64.Encoder encoder = Base64.getMimeEncoder();
    private static int sourceBlack = 0;
    private static int targetBlack = 0;

    private static class Nonogram {
        private final boolean[] cells = new boolean[SIZE_4 * SIZE_4];
        private final int[] magnitudes;

        public Nonogram(String encoded) {
            super();
            byte[] decoded = decoder.decode(encoded);
            for (int i = 0; i < decoded.length; ++ i) {
                for (int j = 0; j < 8; ++ j) {
                    if ((decoded[i] & (1 << (7 - j))) != 0) {
                        int k = i * 8 + j;
                        cells[getPos(k / SIZE, k % SIZE)] = true;
                        ++ sourceBlack;
                    }
                }
            }
            magnitudes = calcMagnitudes();
        }

        private int getPos(int row, int col) {
            return (row + 2) * SIZE_4 + col + 2;
        }

        private int move(int pos, int dir, int count) {
            switch (dir) {
                case E: return pos + count;
                case N: return pos - count * SIZE_4;
                case W: return pos - count;
                case S: return pos + count * SIZE_4;
                default: return pos;
            }
        }

        private int move(int pos, int dir) {
            return move(pos, dir, 1);
        }

        private int[] calcMagnitudes() {
            int[] result = new int[SIZE * 2];
            for (int row = 0; row < SIZE; ++ row) {
                for (int col = 0; col < SIZE; ++ col) {
                    int pos = getPos(row, col);
                    if (cells[pos]) {
                        if (!cells[move(pos, W)]) {
                            ++ result[row + SIZE];
                        }
                        if (!cells[move(pos, N)]) {
                            ++ result[col];
                        }
                    }
                }
            }
            return result;
        }

        private boolean isBlack(int pos) {
            return cells[pos];
        }

        private boolean isWhite(int pos) {
            return !cells[pos];
        }

        private boolean allBlack(int pos, int dir, int count) {
            int p = pos;
            for (int i = 0; i < count; ++ i) {
                if (isWhite(p)) {
                    return false;
                }
                p = move(p, dir);
            }
            return true;
        }

        private boolean allWhite(int pos, int dir, int count) {
            int p = pos;
            for (int i = 0; i < count; ++ i) {
                if (isBlack(p)) {
                    return false;
                }
                p = move(p, dir);
            }
            return true;
        }

        private int findWhite(int pos, int dir) {
            int count = 0;
            int p = pos;
            while (cells[p]) {
                ++ count;
                p = move(p, dir);
            }
            return count;
        }

        @SafeVarargs
        private final void forEach(Function<Integer, Boolean>... processors) {
            outer:
            for (;;) {
                for (Function<Integer, Boolean> processor : processors) {
                    for (int row = 0; row < SIZE; ++ row) {
                        for (int col = 0; col < SIZE; ++ col) {
                            if (processor.apply(getPos(row, col))) {
                                continue outer;
                            }
                        }
                    }
                }
                return;
            }
        }

        private boolean shrink(int pos) {
            if (cells[pos] && cells[move(pos, W)] != cells[move(pos, E)] &&
                    cells[move(pos, N)] != cells[move(pos, S)]) {
                cells[pos] = false;
                return true;
            }
            return false;
        }

        private boolean grow(int pos) {
            if (!cells[pos] && cells[move(pos, W)] != cells[move(pos, E)] &&
                    cells[move(pos, N)] != cells[move(pos, S)]) {
                cells[pos] = true;
                return true;
            }
            return false;
        }

        private boolean moveLine(boolean clockwise, int dir, int sourcePos) {
            int from = (dir + (clockwise ? 1 : 3)) % 4;
            int to = (dir + (clockwise ? 3 : 1)) % 4;
            int opp = (dir + 2) % 4;
            if (isBlack(sourcePos) && isWhite(move(sourcePos, from)) && isWhite(move(sourcePos, dir))) {
                int toCount = findWhite(move(move(sourcePos, dir), to), to) + 1;
                if (allWhite(move(sourcePos, to), to, toCount + 1)) {
                    int lineCount = 1;
                    int tmpPos = move(sourcePos, opp);
                    while (isBlack(tmpPos) && isWhite(move(tmpPos, from)) && allWhite(move(tmpPos, to),  to, toCount + 1)) {
                        ++ lineCount;
                        tmpPos = move(tmpPos, opp);
                    }
                    if (allBlack(tmpPos, to, toCount + 1)) {
                        tmpPos = sourcePos;
                        for (int j = 0; j < lineCount; ++ j) {
                            cells[tmpPos] = false;
                            cells[move(tmpPos, to, toCount)] = true;
                            tmpPos = move(tmpPos, opp);
                        }
                        return true;
                    }
                }
            }
            return false;
        }

        public Nonogram minimize() {
            for (int i = 0; i < 5; ++ i) {
                forEach(pos -> shrink(pos), pos -> moveLine(true, E, pos), pos -> moveLine(true, N, pos),
                        pos -> moveLine(true, W, pos), pos -> moveLine(true, S, pos));
                forEach(pos -> shrink(pos), pos -> moveLine(false, E, pos), pos -> moveLine(false, N, pos),
                        pos -> moveLine(false, W, pos), pos -> moveLine(false, S, pos));
            }
            return this;
        }

        public Nonogram maximize() {
            for (int i = 0; i < 5; ++ i) {
                forEach(pos -> grow(pos), pos -> moveLine(true, E, pos), pos -> moveLine(true, N, pos),
                        pos -> moveLine(true, W, pos), pos -> moveLine(true, S, pos));
                forEach(pos -> grow(pos), pos -> moveLine(false, E, pos), pos -> moveLine(false, N, pos),
                        pos -> moveLine(false, W, pos), pos -> moveLine(false, S, pos));
            }
            return this;
        }

        public String toBase64() {
            if (!Arrays.equals(magnitudes, calcMagnitudes())) {
                throw new RuntimeException("Something went wrong!");
            }
            byte[] decoded = new byte[SIZE * SIZE / 8];
            for (int i = 0; i < decoded.length; ++ i) {
                for (int j = 0; j < 8; ++ j) {
                    int k = i * 8 + j;
                    if (cells[getPos(k / SIZE, k % SIZE)]) {
                        decoded[i] |= 1 << (7 - j);
                        ++ targetBlack;
                    }
                }
            }
            return encoder.encodeToString(decoded);
        }

        @Override
        public String toString() {
            StringBuilder b = new StringBuilder();
            for (int row = 0; row < SIZE; ++ row) {
                for (int col = 0; col < SIZE; ++ col) {
                    b.append(cells[getPos(row, col)] ? '#' : ' ');
                }
                b.append('\n');
            }
            return b.toString();
        }
    }

    public static void main(String[] args) throws Exception {
        try (BufferedWriter writer = new BufferedWriter(new FileWriter("solutions_b64.txt"));
                BufferedReader reader = new BufferedReader(new FileReader("nonograms_b64.txt"))) {
            String line;
            while ((line = reader.readLine()) != null) {
                writer.write(new Nonogram(line).minimize().toBase64() + "\n");
                //writer.write(new Nonogram(line).maximize().toBase64() + "\n");
            }
        }
        System.out.printf("%d -> %d", sourceBlack, targetBlack);
    }
}

1

Bash - 10.239.288 hình vuông

Là một giải pháp tham khảo cuối cùng:

cp nonograms_b64.txt solutions_b64.txt

Vì chương trình của bạn được phép trả lại cùng một lưới nếu nó không thể tìm ra giải pháp tốt hơn, nên việc in ra toàn bộ nguyên văn tệp cũng hợp lệ.

Có tổng số 10.239.288 ô vuông màu đen trong tệp thử nghiệm, khá gần với 10.240.000 bạn mong đợi từ 80% các ô vuông được điền vào trong số 50.000 ô vuông với 256 ô vuông mỗi ô. Như thường lệ với các câu hỏi về pin thử nghiệm của tôi, tôi đã chọn số lượng các trường hợp thử nghiệm với kỳ vọng rằng điểm số tối ưu sẽ nằm trong khoảng 2 triệu, mặc dù tôi nghi ngờ điểm số sẽ gần hơn 4 hoặc 5 triệu trong khoảng thời gian này .


Nếu bất cứ ai cũng có thể tạo ra một giải pháp tối đa hóa các ô vuông màu đen thay vì thu nhỏ chúng và quản lý để có được hơn 10.240.000, tôi có thể xem xét cho nó một khoản tiền thưởng.


1

Matlab, 7.270.894 hình vuông (~ 71% so với bản gốc)

Ý tưởng là một tìm kiếm tham lam lặp đi lặp lại đơn giản: Đối với mỗi hình vuông màu đen, hãy thử nếu bạn có thể đặt nó thành màu trắng mà không thay đổi cường độ phi hình học. Lặp lại điều này hai lần. (Bạn có thể đạt được kết quả tốt hơn với nhiều lần lặp lại, nhưng không miễn phí: Kết quả là thời gian chạy dài hơn tương ứng. Bây giờ là khoảng 80 phút. Tôi sẽ làm điều đó, nếu chúng ta không phải tính toán tất cả 50 nghìn testcase ...)

Đây là mã (mỗi chức năng trong một tệp riêng biệt, như thường lệ.)

function D = b64decode(E)
% accepts a string of base 64 encoded data, and returns a array of zeros
% and ones
F = E;
assert( mod(numel(E),4)==0 && 0 <= sum(E=='=') && sum(E=='=') <= 2,'flawed base 64 code')

F('A' <= E & E<= 'Z') = F('A' <= E & E<= 'Z') -'A';       %upper case
F('a' <= E & E<= 'z') = F('a' <= E & E<= 'z') -'a' + 26;  %lower case
F('0'<= E & E <= '9') = F('0'<= E & E <= '9') -'0' + 52;  %digits
F( E == '+') = 62;
F( E == '/') = 63;
F( E == '=') = 0;

D=zeros(1,numel(E)*3*8/4);

for k=1:numel(E);
    D(6*(k-1)+1 + (0:5)) = dec2bin(F(k),6)-'0';
end

if E(end) == '=';
    D(end-7:end) = [];
    if E(end-1) == '=';
        D(end-7:end) = [];
    end
end
end

function E = b64encode(D)
assert(mod(numel(D),8)==0,'flawed byte code');
N=0;
while mod(numel(D),6) ~= 0;
    D = [D,zeros(1,8)];
    N = N+1;
end
dict = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';

E=zeros(1,numel(D)/6)+'=';
for k=0:numel(E)-N-1;
    E(k+1) = dict(bin2dec( [D(6*k+(1:6))+'0',''] ) + 1);
end

E = [E,''];
end


function [B,T,O] = reduce_greedy(N)
% reduce greedily
NM = nomographic_magnitude(N);
B = N; %current best
M = N;
T = nnz(N); %current number of filled squares
O = T;

for r=1:2;  %how many repetitions
    I = find(B);
    for k=1:numel(I);
        M = B;
        M( I(k) ) = 0;
        %check whether we have a valid solution
        if all(NM == nomographic_magnitude(M))
            if T > nnz(M); %did we actually reduce the number of filled squares?
                B = M;
                T = nnz(M);
            end
        end
    end
end


%% main file
string_in = fileread('nonograms_b64.txt');
string_out = string_in;
tic
total_new = 0;  %total number of black squares
total_old = 0;
M = 50000;
for k=1:M;
    disp(k/M); %display the progress
    line = string_in(45*(k-1)+(1:44));
    decoded = b64decode(line);        
    nonogram = reshape(decoded,16,[]) ;%store nonogram as a matrix
    [B,T,O] = reduce_greedy(nonogram);
    disp([nnz(B),nnz(nonogram)])
    total_new = total_new + T;
    total_old = total_old + O;
    string_in(45*(k-1)+(1:44)) = b64encode(B(:).');
end
toc
F = fopen('nonograms_b64_out.txt','w');
fprintf(F,string_out);
%%
disp([total_new,total_old])
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.