Python 2 & PuLP - 2,644,688 กำลังสอง (ลดให้เหมาะสม); 10,753,553 สี่เหลี่ยม (ขยายใหญ่สุด)
golfed น้อยที่สุดถึง 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
(NB: เส้นที่เยื้องอย่างหนักเริ่มต้นด้วยแท็บไม่ใช่ที่ว่าง)
เอาต์พุตตัวอย่าง: https://drive.google.com/file/d/0B-0NVE9E8UJiX3IyQkJZVk82Vkk/view?usp=sharing
ปรากฎว่าปัญหาเช่นสามารถแปลงเป็นโปรแกรมเชิงเส้นจำนวนเต็มได้อย่างง่ายดายและฉันต้องการปัญหาพื้นฐานเพื่อเรียนรู้วิธีใช้ PuLP - อินเทอร์เฟซหลามสำหรับนักแก้ปัญหา LP หลากหลาย - สำหรับโครงการของฉันเอง มันกลับกลายเป็นว่า PuLP นั้นใช้งานง่ายมากและตัวสร้าง LP ที่ไม่ได้รับการทำงานนั้นสมบูรณ์แบบในครั้งแรกที่ฉันลองใช้
สองสิ่งที่ดีเกี่ยวกับการใช้ตัวแก้ปัญหา IP แบบ จำกัด ขอบเขตเพื่อทำงานอย่างหนักในการแก้ปัญหานี้สำหรับฉัน (นอกเหนือจากการไม่ใช้งานตัวแก้ปัญหาแบบสาขาและแบบ จำกัด ขอบเขต) คือ
- ตัวแก้วัตถุประสงค์ที่สร้างขึ้นนั้นรวดเร็วมาก โปรแกรมนี้แก้ปัญหาทั้งหมด 50,000 ปัญหาภายใน 17 ชั่วโมงบนพีซีที่บ้านของฉันค่อนข้างต่ำ แต่ละตัวอย่างใช้เวลาตั้งแต่ 1-1.5 วินาทีเพื่อแก้ปัญหา
- พวกเขาผลิตโซลูชั่นที่ดีที่สุดที่รับประกันได้ (หรือบอกคุณว่าพวกเขาไม่สามารถทำได้) ดังนั้นฉันจึงมั่นใจได้ว่าจะไม่มีใครเอาชนะคะแนนของฉันในรูปสี่เหลี่ยม (แม้ว่าบางคนอาจผูกมันและเอาชนะฉันในส่วนของการเล่นกอล์ฟ)
วิธีใช้โปรแกรมนี้
ก่อนอื่นคุณจะต้องติดตั้ง PuLP pip install pulp
ควรทำเคล็ดลับถ้าคุณติดตั้งจุดเล็ก ๆ
จากนั้นคุณจะต้องใส่ข้อมูลต่อไปนี้ในไฟล์ชื่อ "c": https://drive.google.com/file/d/0B-0NVE9E8UJiNFdmYlk1aV9aYzQ/view?usp=sharing
จากนั้นให้รันโปรแกรมนี้ในการสร้าง Python 2 ที่ล่าช้าจากไดเรกทอรีเดียวกัน ในเวลาน้อยกว่าหนึ่งวันคุณจะมีไฟล์ชื่อ "s" ซึ่งบรรจุกริด nonogram ที่แก้ไขแล้ว 50,000 รายการ (ในรูปแบบที่อ่านได้) โดยแต่ละไฟล์มีจำนวนสี่เหลี่ยมที่เติมเต็มแสดงอยู่ด้านล่าง
หากคุณต้องการเพิ่มจำนวนสี่เหลี่ยมที่เต็มแล้วแทนให้เปลี่ยนLpMinimize
บรรทัดที่ 8 เป็นLpMaximize
แทน คุณจะได้ผลลัพธ์มากเช่นนี้: https://drive.google.com/file/d/0B-0NVE9E8UJiYjJ2bzlvZ0RXcUU/view?usp=sharing
รูปแบบอินพุต
โปรแกรมนี้ใช้รูปแบบอินพุตที่แก้ไขเนื่องจาก Joe Z. กล่าวว่าเราจะได้รับอนุญาตให้เข้ารหัสรูปแบบอินพุตอีกครั้งหากเราต้องการแสดงความคิดเห็นใน OP คลิกลิงก์ด้านบนเพื่อดูว่าหน้าตาเป็นอย่างไร ประกอบด้วย 10,000 บรรทัดแต่ละบรรทัดมี 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
นี่คือโปรแกรมที่สร้าง "ตัวอย่างเอาต์พุต" ที่ลิงก์ด้านบน ดังนั้นสตริงที่ยาวเป็นพิเศษในตอนท้ายของแต่ละตารางซึ่งฉันตัดทอนเมื่อเล่นกอล์ฟ (เวอร์ชัน golfed ควรสร้างผลลัพธ์ที่เหมือนกันลบด้วยคำ"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
0
และ□
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 ของเซลล์และตัวคั่นโดยตรงไปทางซ้ายในแถวของมันและอยู่เหนือมันในคอลัมน์ ฉันมีข้อ จำกัด ที่จำลอง 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 จากนั้นจัดรูปแบบโซลูชันที่เกิดขึ้นขณะที่เขียนไปยังไฟล์