Побудуйте неографічний оптимізатор величини ™


12

Нонограма - це японська гра-головоломка, в якій мета - намалювати чорно-білу картину відповідно до переліку сусідніх регіонів, наприклад:

Приклад нонограми з "лямбда".

Визначте неографічну величину рядка чи стовпця, щоб це було число суміжних чорних областей у цьому рядку чи стовпці. Наприклад, верхній ряд має нографічну величину 1, оскільки в цьому ряду є одна область з 2 квадратів. 8-й ряд має неографічну величину 3, оскільки має 2, 2, 1.

Порожній рядок або стовпець має неографічну величину 0.


Ваше завдання - написати програму, яка приймає сітку рішення для нонограми, і виробляє сітку рішення з якомога менше заповнених квадратів. де кожен рядок і стовпець має той самий нонографічний магнітуд, як і дана сітка рішення.

Наприклад, сітка нонограм із усіма заповненими квадратами має неографічну величину 1 у кожному рядку чи стовпці:

Нонограма 10х10, де заповнюється кожен квадрат.

Таку ж нонографічну величину можна було досягти, лише провівши діагональну смугу через сітку, різко зменшивши кількість заповнених квадратів:

10 -10 нонограма з такою ж нонографічною величиною, що і вище.


Ваша програма отримає вхід із цього файлу з 50 000 рядків (1,32 Мб текстовий файл tar.gz; нерозпакований 2,15 Мб), кожен з яких представляє собою одну сітку 16 × 16 нонограмних розчинів із випадковими (80% чорними) заповненими квадратами та вивести ще 50 000 рядків, кожна з яких містить оптимізовану сітку рішення для відповідної вхідної сітки.

Кожна сітка представлена ​​у вигляді рядка base64 з 43 символами (кодування квадратів зліва направо, потім зверху вниз), і вашій програмі потрібно буде повернути результат у тому ж форматі. Наприклад, перша сітка у файлі є E/lu/+7/f/3rp//f799xn/9//2mv//nvj/bt/yc9/40=і відображається як:

Перший приклад нонограми

Сітка починається з того, на Eякому відображається карта 000100, тому перші шість комірок у верхньому ряду всі білі, крім четвертої. Наступний символ - це, до /яких карт 111111, тому наступні 6 комірок усі чорні - і так далі.


Ваша програма фактично повинна повернути сітку рішення з правильними нонографічними величинами для кожного з 50 000 тестових випадків. Дозволено повернути ту саму сітку, що і вхід, якщо нічого кращого не було знайдено.

Програма, яка повертає найменше загальних заповнених квадратів (будь-якою мовою), є переможцем, причому коротший код є краваткою.


Поточний табло:

  1. 3,637,260 - Sleafar, Java
  2. 7,270,894 - недолік, Матлаб
  3. 10,239,288 - Джо З., Баш

1
Я не бачу сенсу кодування базової 64 і робота з цим - це справжній біль. Чи не було б простіше просто зробити рядки з нулів і нулів? Або кодувати всю справу як растрову карту?
невдача

@flawr: В основному це зменшує розмір файлів (в 6 разів порівняно з 1 та 0). Крім того, з растровими зображеннями буде ще важче працювати.
Джо З.

Ви можете просто зробити чорно-біле зображення, просте для читання / запису та такого ж розміру, як кодування b64.
невдача

2
також не є вентилятором кодування b64 для введення та / або виводу. чому б просто не дозволити вводу-виводу в будь-якому зручному форматі?
Спарр

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

Виявляється, такі проблеми, як легко перетворюються в лінійні програми Integer, і мені потрібна була основна проблема, щоб навчитися використовувати PuLP - інтерфейс пітона для різноманітних рішень для LP - для власного проекту. Виявляється також, що PuLP надзвичайно простий у використанні, і необурений будівельник LP працював ідеально в перший раз, коли я його спробував.

Дві приємні речі щодо використання розв'язуваного IP-вирішувача для виконання важкої роботи для вирішення цього питання (крім того, що не потрібно реалізовувати гілку та прив’язаний вирішувач) полягають у тому, що

  • Цільові розв'язувачі дійсно швидкі. Ця програма вирішує всі 50000 проблем приблизно за 17 годин на моєму відносно низькому домашньому ПК. Кожен екземпляр займав від 1-1,5 секунди для вирішення.
  • Вони виробляють гарантовані оптимальні рішення (або скажуть, що вони цього не зробили). Таким чином, я можу бути впевнений, що ніхто не буде бити мою оцінку в квадратах (хоча хтось може зв'язати це і побити мене в гольф-частині).

Як користуватися цією програмою

По-перше, вам потрібно буде встановити PuLP. pip install pulpслід виконати трюк, якщо у вас встановлений файл pip.

Потім у файл, який називається "c", потрібно буде поставити таке: https://drive.google.com/file/d/0B-0NVE9E8UJiNFdmYlk1aV9aYzQ/view?usp=sharing

Потім запустіть цю програму в будь-якій пізній збірці Python 2 з того самого каталогу. Менш ніж за день у вас з'явиться файл під назвою "s", який містить 50 000 вирішених сіток нонограмів (у читаному форматі), кожна із загальною кількістю заповнених квадратів, перелічених під ним.

Якщо ви хочете максимально збільшити кількість заповнених квадратів, замініть LpMinimizeна рядок 8 на LpMaximize. Ви отримаєте результат дуже схожий на це: https://drive.google.com/file/d/0B-0NVE9E8UJiYjJ2bzlvZ0RXcUU/view?usp=sharing

Формат введення

Ця програма використовує модифікований формат введення, оскільки Джо Z. сказав, що нам буде дозволено перекодувати формат введення, якщо нам це подобається в коментарі до ОП. Клацніть посилання вище, щоб побачити, як воно виглядає. Він складається з 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 додаткова інформація про сітки, які використовувались для генерації величин, абсолютно марно. Величини - єдине обмеження, і тому величини - це все, до чого мені потрібен доступ.

Незабудований 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)

Я використовую сітку 18х18, центральна частина 16х16 - фактичне рішення головоломки. cellsце сітка. Перший рядок створює 324 бінарних змінних: "cell_0_0", "cell_0_1" тощо. Я також створюю сітки "пробілів" між клітинками та навколо них у частині розчину сітки. rowsepsвказує на 289 змінних, які символізують пробіли, які розділяють комірки по горизонталі, тоді як colsepsаналогічно вказують на змінні, що позначають пробіли, які розділяють клітинки вертикально. Ось схема 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

В 0и і з є двійкові значення відслідковуються 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,""

Це просто встановлює комірки навколо зовнішнього краю сітки до нуля (саме тому я представляв їх як нулі вище). Це найдоцільніший спосіб відстежити, скільки "блоків" комірок заповнено, оскільки це забезпечує, щоб кожна зміна від незаповненої до заповненої (переміщення по стовпцю чи рядку) відповідала відповідній зміні з заповненої на незаповнену (і навпаки ), навіть якщо перша чи остання комірка в рядку заповнена. Це єдина причина використання сітки 18х18 в першу чергу. Це не єдиний спосіб підрахунку блоків, але я думаю, що це найпростіший.

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

Це справжнє м'ясо логіки ІЛП. В основному це вимагає, щоб кожна комірка (окрім тих, що перебувають у першому рядку та стовпці), була логічним xor комірки та роздільника безпосередньо зліва в рядку та безпосередньо над ним у своєму стовпчику. Я отримав обмеження, що імітують xor у {0,1} цілій програмі з цієї чудової відповіді: /cs//a/12118/44289

Щоб пояснити трохи більше: це обмеження xor робить його таким чином, що роздільники можуть бути 1, якщо і лише тоді, коли вони лежать між клітинками, які дорівнюють 0 і 1 (позначаючи зміну від незаповненої до заповненої або навпаки). Таким чином, буде рівно вдвічі більше 1-значних роздільників у рядку чи стовпчику, ніж кількість блоків у цьому рядку чи стовпці. Іншими словами, сума роздільників для даного рядка або стовпця рівно вдвічі перевищує величину цього рядка / стовпця. Звідси такі обмеження:

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

@tigrou Дякую Я не впевнений, що проблема повені є достатньо лінійною, щоб визнати таке рішення. Я, звичайно, не бачу, як моделювати це таким чином.
Кінтопія

Здається, хтось уже спробував це: kunigami.blog/2012/09/16/flood-it-an-exact-approach Однак вони не змогли оптимально підібрати рішення у можливий час для дошки 14x14.
tigrou

3

Java, 6,093,092 4,332,656 3,637,260 квадратів (мінімізовано), 10,567,550 10,567,691 10,568,746 квадратів (максимально)

Обидва варіанти програми неодноразово виконують операції над вихідною сіткою, не змінюючи величини.

Мінімізатор

скорочуватися ()

введіть тут опис зображення

Якщо чорний квадрат має 2 білих сусіда та 2 чорних сусіда під кутом 90 °, його можна замінити білим квадратом.

moveLine ()

введіть тут опис зображення введіть тут опис зображення

У таких конфігураціях, як вище, чорна лінія може бути переміщена праворуч. Це робиться неодноразово для всіх 4 напрямків лінії за годинниковою стрілкою та проти годинникової стрілки, щоб відкрити нові можливості зменшення.

Максимізатор

Відключіть рядок main()і прокоментуйте рядок над ним для цієї версії.

рости ()

введіть тут опис зображення

Якщо білий квадрат має 2 білих сусіда та 2 чорних сусіда під кутом 90 °, його можна замінити чорним квадратом.

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 чорних квадратів, що досить близько до 10 240 000, яких можна було б очікувати від 80% квадратів, заповнених із 50 000 сіток з 256 квадратами. Як зазвичай у моїх питаннях із тестовою батареєю, я вибрав кількість тестових випадків, розраховуючи, що оптимальні показники будуть приблизно в 2-мільйонний діапазон, хоча я підозрюю, що показники цього разу будуть ближче до 4 або 5 мільйонів .


Якщо хтось може створити рішення, яке максимально збільшує чорні квадрати, а не мінімізує їх і вдається отримати понад 10 240 000, я можу подумати про те, щоб дати йому щедрість.


1

Матлаб, 7 278 894 квадратів (~ 71% від оригіналу)

Ідея - це простий повторний жадібний пошук: для кожного чорного квадрата спробуйте, чи зможете ви встановити його на білий, не змінюючи нонографічні величини. Повторіть це двічі. (Ви можете домогтися кращих результатів за допомогою більшої кількості повторень, але не безкоштовно. Це призводить до відповідно тривалішого виконання. Тепер це пройшло приблизно 80 хвилин. Я б це зробив, якби нам не довелося обчислювати всі 50к тестів ...)

Тут код (кожна функція в окремому файлі, як зазвичай).

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])
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.