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, а потім форматує отримане рішення, як він записує його у файл.