Nonographic Magnitude Optimizer™を構築する


12

ノノグラムは、次のように、隣接する領域のリストに従って白黒の絵を描くことが日本のパズルゲームです。

「ラムダ」のあるノノグラムの例。

行または列の非グラフィックの大きさを、その行または列の連続する黒い領域の数に定義します。たとえば、その行には2つの正方形の領域が1つあるため、一番上の行の非グラフィックの大きさは1です。8行目には、2、2、1があるため、3の非グラフィックの大きさがあります。

空の行または列の非グラフィックの大きさは0です。


あなたの仕事は、ノノグラムのソリューショングリッドを取得し、すべての行と列が特定のソリューショングリッドと同じ非グラフィックマグナタイドを持つ、できるだけ少ない正方形でソリューショングリッドを生成するプログラムを作成することです。

たとえば、すべての正方形が塗りつぶされた非グラムグリッドは、すべての行または列で1の非グラフィックマグニチュードを持ちます。

すべての正方形が塗りつぶされている10x10の非グラム。

グリッドに斜めのストライプを配置するだけで、同じ非グラフィックの大きさを実現でき、塗りつぶされた正方形の数を劇的に削減できます。

上記と同じ非地理的大きさの10x10の非グラム。


プログラムは、このファイルから50,000行で構成される入力を受け取ります(1.32 MB tar.gzテキストファイル; 2.15 MB解凍)。各行は、ランダムに(80%黒)塗りつぶされた正方形の単一の16×16ノノグラムソリューショングリッドを表し、さらに50,000行を出力します。各行には、対応する入力グリッドの最適化されたソリューショングリッドが含まれています。

各グリッドは、43文字のbase64文字列(左から右、次に上から下にエンコードする正方形)として表され、プログラムは同じ形式で出力を返す必要があります。たとえば、ファイルの最初のグリッドはE/lu/+7/f/3rp//f799xn/9//2mv//nvj/bt/yc9/40=であり、次のようにレンダリングされます。

最初のノノグラムの例

グリッドはEにマップされる000100で始まるため、一番上の行の最初の6つのセルは、4番目を除いてすべて白です。次の文字がある/にマッピングします111111ため、次の6つのセルはすべて黒になります。


プログラムは、50,000個のテストケースのそれぞれについて、正確な非地理的規模でソリューショングリッドを実際に返す必要があります。より良いものが見つからない場合は、入力と同じグリッドを返すことができます。

最小の合計塗りつぶされた正方形(任意の言語)を返すプログラムが勝者であり、短いコードがタイブレイカーです。


現在のスコアボード:

  1. 3,637,260 — Sleafar、Java
  2. 7,270,894 — flawr、Matlab
  3. 10,239,288 —ジョーZ.、バッシュ

1
Base 64エンコードのポイントは実際にはわからず、それを扱うのは本当に苦痛です。1と0の行を作成する方が簡単ではないでしょうか?または、全体をビットマップとしてエンコードしますか?
フレイ

@flawr:主にファイルサイズを縮小します(1と0に比べて6倍)。また、ビットマップの操作はさらに難しくなります。
ジョーZ.

白黒の画像を作成し、読み取り/書き込みが簡単で、b64エンコーディングと同じサイズにすることができます。
フレイ

2
また、入力および/または出力用のb64エンコーディングのファンではありません。I / Oを便利な形式にするだけではどうですか?
スパー

1
そうだとすれば、明日までに最適な解決策が得られるはずです。
キントピア

回答:


7

Python 2およびPuLP — 2,644,688平方(最適化された最小化); 10,753,553平方(最適化された最大化)

1152バイトまで最小限にゴルフ

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

(注意:大きくインデントされた行は、スペースではなくタブで始まります。)

出力例:https : //drive.google.com/file/d/0B-0NVE9E8UJiX3IyQkJZVk82Vkk/view?usp=sharing

のような問題は整数線形プログラムに容易に変換可能であることがわかり、私は独自のプロジェクトでさまざまなLPソルバー用のPythonインターフェイスであるPuLPを使用する方法を学ぶための基本的な問題が必要でした。また、PuLPは非常に使いやすく、最初に試してみたときに、未開発のLPビルダーが完全に機能したことがわかりました。

ブランチアンドバウンドIPソルバーを使用してこれを解決するためのハードワークを行うことに関する2つの良い点(ブランチアンドバウンドソルバーを実装する必要がないこと)

  • 専用のソルバーは非常に高速です。このプログラムは、比較的低価格の自宅のPCで約17時間で50000件の問題をすべて解決します。各インスタンスの解決には1〜1.5秒かかりました。
  • 保証された最適なソリューションを生成します(または、そうしなかったことを伝えます)。したがって、私は誰も私のスコアを四角で打つことはないと確信できます(誰かがそれを結びつけてゴルフの部分で私を打つかもしれませんが)。

このプログラムの使用方法

まず、PuLPをインストールする必要があります。pip install pulppipがインストールされている場合は、トリックを行う必要があります。

次に、「c」というファイルに次のファイルを配置する必要があります。https//drive.google.com/file/d/0B-0NVE9E8UJiNFdmYlk1aV9aYzQ/view?usp = sharing

次に、同じディレクトリからPython 2以降のビルドでこのプログラムを実行します。1日も経たないうちに、50,000個の解かれた非グラムグリッド(読み取り可能な形式)を含む「s」というファイルがあり、それぞれの下にリストされている塗りつぶされた正方形の総数があります。

代わりに塗りつぶされた正方形の数を最大化する場合は、代わりにLpMinimize8行目を変更しLpMaximizeます。次のような出力が得られます:https : //drive.google.com/file/d/0B-0NVE9E8UJiYjJ2bzlvZ0RXcUU/view?usp=sharing

入力形式

Joe Z.がOPのコメントで必要に応じて入力形式を再エンコードできると述べたため、このプログラムは修正された入力形式を使用します。上のリンクをクリックして、表示を確認します。10000行で構成され、各行には16個の数字が含まれています。偶数行は特定のインスタンスの行の大きさであり、奇数行はその上の行と同じインスタンスの列の大きさです。このファイルは、次のプログラムによって生成されました。

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

(この再エンコードプログラムは、上記の同じプロジェクト用に作成したカスタムBitQueueクラスをテストする機会も与えてくれました。これは、データをビットまたはバイトのシーケンスとしてプッシュしたり、一度にビットまたはバイトのいずれかでポップされます。この例では、完全に機能しました。)

ILPを構築する特定の理由で入力を再エンコードしましたが、振幅の生成に使用されたグリッドに関する追加情報はまったく役に立たないためです。大きさは唯一の制約であるため、大きさは私がアクセスする必要があるすべてです。

Ungolfed ILPビルダー

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

これは、上記でリンクされた「出力例」を実際に生成したプログラムです。したがって、各グリッドの最後にある余分な長い文字列は、ゴルフをするときに切り捨てました。(ゴルフバージョンは、単語を除いた同一の出力を生成するはずです"Filled squares for "

使い方

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)

18x18グリッドを使用し、中央の16x16部分が実際のパズルソリューションです。cellsこのグリッドです。最初の行は、324個のバイナリ変数「cell_0_0」、「cell_0_1」などを作成します。また、グリッドのソリューション部分のセル間およびセルの周囲に「スペース」のグリッドを作成します。rowsepsは、セルを水平に区切るスペースを象徴する289個の変数をcolseps指し、同様に、セルを垂直に区切るスペースをマークする変数を指します。これがユニコード図です:

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

0sおよびsはによって追跡バイナリ値であるcell変数|Sは、バイナリ値によって追跡されrowsepた変数、及び-Sは、バイナリ値によって追跡されるcolsep変数。

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

これが目的関数です。すべてのcell変数の合計。これらはバイナリ変数であるため、これはソリューションの塗りつぶされた正方形の数とまったく同じです。

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

これは、グリッドの外縁の周りのセルをゼロに設定するだけです(そのため、上記のセルをゼロとして表しています)。これは、セルの「ブロック」がいくつ埋められたかを追跡する最も便利な方法です。これは、未記入から記入へのすべての変更(列または行の移動)が、記入から未記入への対応する変更(およびその逆)と一致することを保証するためです)、行の最初または最後のセルが埋められた場合でも。これが、最初に18x18グリッドを使用する唯一の理由です。ブロックを数える唯一の方法ではありませんが、最も簡単だと思います。

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,""

これがILPのロジックの本質です。基本的に、各セル(最初の行と列のセルを除く)は、セルの論理xorであり、行の左に直接あり、列のすぐ上にあります。この素晴らしい答えから、{0,1}整数プログラム内のxorをシミュレートする制約を取得しました:https : //cs.stackexchange.com/a/12118/44289

もう少し説明すると、このxor制約は、0と1のセルの間にある場合にのみセパレーターを1にできるようにします(未入力から入力へ、またはその逆への変更をマーク)。したがって、行または列の1値の区切り文字は、その行または列のブロック数のちょうど2倍になります。つまり、特定の行または列の区切り文字の合計は、その行/列の大きさのちょうど2倍です。したがって、次の制約があります。

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,""

そしてそれはほとんどそれです。残りは、デフォルトのソルバーにILPを解決するように要求し、結果のソリューションをファイルに書き込むときにフォーマットします。


本当に良い答え。LPソルバーについて学びたいです。19x19、6色のボード(ソリューションの計算時間に関して)のフラッドイットパズル(リンク)の解決に使用できると思いますか?私はすでにそのコンテストに回答しました(そして、勝ちました)が、私の方法(A *検索アルゴリズム)は最適でない解しか与えません。
ティグル

@tigrouありがとう。洪水の問題がそのような解決策を認めるほど線形であるかどうかはわかりません。この方法でモデル化する方法がわかりません。
キントピア

誰かがすでにそれを試しているようです:kunigami.blog/2012/09/16/flood-it-an-exact-approachしかし、彼らは14x14のボードのために実行可能な時間で最適な解決策を見つけることができませんでした。
ティグロー

3

Java、6,093,092 4,332,656 3,637,260スクエア(最小化)、10,567,550 10,567,691 10,568,746スクエア(最大化)

プログラムの両方のバリアントは、大きさを変えずに、ソースグリッドで繰り返し操作を実行します。

ミニマイザー

シュリンク()

ここに画像の説明を入力してください

黒い正方形に2つの白い隣人と90°の角度で2つの黒い隣人がある場合、白い正方形に置き換えることができます。

moveLine()

ここに画像の説明を入力してください ここに画像の説明を入力してください

上記のような構成では、黒い線を右に移動できます。これは、時計回りと反時計回りの4つのライン方向すべてに対して繰り返し行われ、新しいシュリンクの可能性を開きます。

マキシマイザー

main()このバージョンでは、行のコメントを外し、その上の行をコメント化します。

grow()

ここに画像の説明を入力してください

白い正方形に90°の角度で2つの白い隣人と2つの黒い隣人がある場合、黒い正方形に置き換えることができます。

moveLine()

Minimizerと同じです。

ソース

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

バッシュ— 10,239,288マス

最下位の参照ソリューションとして:

cp nonograms_b64.txt solutions_b64.txt

より良い解決策が見つからない場合、プログラムは同じグリッドを返すことが許可されているため、ファイル全体を逐語的に印刷することも有効です。

テストファイルには合計10,239,288の黒い四角があります。これは、それぞれ256の正方形で50,000のグリッドから塗りつぶされた正方形の80%から予想される10,240,000に非常に近いものです。私のテストバッテリーの質問でいつものように、私は最適なスコアが約200万の範囲になることを期待してテストケースの数を選択しましたが、今回はスコアが400万または500万に近づくと思われます。


誰もがソリューションを作成できる場合 黒い正方形を最小化するのではなく最大化し、10,240,000を超えるように管理、私はそれを報奨金として与えることを検討するかもしれません。


1

Matlab、7,270,894平方(元の〜71%)

アイデアは単純な貪欲な検索の繰り返しです。すべての黒い四角について、非グラフィックの大きさを変更せずに白に設定できるかどうか試してください。これを2回繰り返します。(より多くの繰り返しでより良い結果を得ることができますが、無料ではありません:それに応じてランタイムが長くなります。今では約80分です。すべての50kテストケースを計算する必要がなければ...)

ここで、コード(通常どおり、各関数は個別のファイルにあります。)

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])
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.