#!/usr/bin/env python3

# program to simply rule out certain numbers of diags

import sys
import random
import re

def mean(l):
return float(sum(l)) / max(len(l), 1)

# numbers = [int(s) for s in re.findall('\d+', sys.stdin.read())]

if len(sys.argv) < 4:
print("Usage:", sys.argv[0],end="")
print("  n mininfile seed")
sys.exit(0)

n = int(sys.argv[1])
mininfile = int(sys.argv[2])
random.seed(int(sys.argv[3]))

# setup data structures
rows = [ 0 for i in range(n) ]
cols = [ 0 for i in range(n) ]
numinrow = [ n for i in range(n) ]
numincol = [ n for i in range(n) ]
d1 = [ 0 for i in range(2*n-1) ]
d2 = [ 0 for i in range(2*n-1) ]
diags1 = [i for i in range(2*n-1)]
diags2 = [i for i in range(2*n-1)]
random.shuffle(diags1)
random.shuffle(diags2)

outdiags = []

# direction = +1 or -1. +1 for sum  diagonal and -1 for difference
# sum diag d = r+c.  So c = d-r
# diff diag d = r-c+n-1. So c = r-d+n-1

def updatediag(d,direction):
global d1
global d2
global outdiags
assert(direction == 1 or direction == -1)
killedrows = []
killedcols = []
for row in range(n):
if direction == 1:
col = d-row
else:
col = row-d+n-1
if col in range(n):
if noattack([row,col]):
if numinrow[row] <= mininfile or numincol[col] <= mininfile:
return False
killedrows.append(row)
killedcols.append(col)
#print(d," ",direction," ", killedrows, killedcols)
#not returned FALSE so actually do it
for row in killedrows :
numinrow[row] -= 1
for col in killedcols :
numincol[col] -= 1
if direction == +1:
d1[d] = 1
outdiags.append([d,1])
else:
d2[d] = 1
outdiags.append([d,0])
return True

def generatediags():
global diags1
global diags2
numsofar = 0
while((diags1 or diags2) and (numsofar < numdiags)):
direction=random.randrange(-1,2,2) # i.e. -1 or +1
if direction== 1 and diags1:    # i.e. there is a diagonal to use
d=diags1.pop()
else:                           # use other diagonal
direction=-1
d=diags2.pop()
if updatediag(d,direction):
numsofar += 1
return(numsofar == numdiags)

def generatediags_all():
global diags1
global diags2
numsofar = 0
while((diags1 or diags2)):
if not diags1:
direction= -1
elif not diags2:
direction= 1
else:
direction=random.randrange(-1,2,2) # i.e. -1 or +1
if direction== 1:    # i.e. using sum diagonal
d=diags1.pop()
else:                           # use other diagonal
d=diags2.pop()
if updatediag(d,direction):
numsofar += 1

numSatVars = 0

numargs = int(len(numbers)-1)
for i in range(int(numargs/2)):
queenx=int(numbers[i*2+1])
queeny=int(numbers[i*2+2])
if noattack([queenx,queeniy]):
rows[queenx] = 1
cols[queeny] = 1
d1[queenx+queeny] = 1
d2[queenx-queeny+n-1] = 1
else:
outputFalseSatInstance()

def noattack(q):
return (rows[q[0]] == 0 and
cols[q[1]] == 0 and
(d1[q[0]+q[1]] == 0) and
(d2[q[0]-q[1]+n-1] == 0) )

def queen2var(q):
global numSatVars
rawInt = q[0]*n+q[1]
if rawInt not in int2var:
numSatVars += 1
qv = len(int2var)+1
int2var[rawInt] = qv
var2queen[qv] = q
return int2var[rawInt]

def processqueens():
global outrows
global outcols
global outd1
global outd2
global openrows
global opencols
outrows = [ [] for i in range(n) ]
outcols = [ [] for i in range(n) ]
outd1 = [ [] for i in range(2*n-1) ]
outd2 = [ [] for i in range(2*n-1) ]
openrows = []
opencols = []
for i in range(n):
if rows[i] == 0:
openrows.append(i)
if cols[i] == 0:
opencols.append(i)
for i in openrows:
for j in opencols:
q=[i,j]
if noattack(q):
qv = queen2var(q)
outrows[i].append(qv)
outcols[j].append(qv)
outd1[i+j].append(qv)
outd2[i-j+n-1].append(qv)

int2var = {}
var2queen = {}

def outputFalseSatInstance():
print("c Unsatisfiability detected in preprocessing")
print("c ")
print("p cnf 1 1")
print("0")
sys.exit(0)

def outputComplexFalseSatInstance():
print("c This instance contains a row or column where no queens can be placed")
outputFalseSatInstance()

#### Commander Encoding

clauses = []
maxSize = 3

def varAlloc():
global numSatVars
numSatVars += 1
return numSatVars

def makeCommanderClauses(varList,commander):
global clauses
numVars = len(varList)
# if commander true then at least one is true
c1 = list(map(lambda x: x,varList))
c1.append(-commander)
clauses.append(c1)
# if commander is false then all false
for var in varList:
clauses.append([commander,-var])
# naive at most one for vars
for i in range(numVars-1):
for j in range(i+1,numVars):
clauses.append([-varList[i],-varList[j]])

# returns variable which is equivalent to the or of variables in the varList
# adds needed clauses to the global clauses list to enforce this and that at most one of varlist is true

def makeCommanderFromVars(varList):
if not isinstance(varList,list): # assume that input is a single variable
return varList
numVars = len(varList)
if numVars == 1:
return varList[0]           # no extra clauses necessary
commander = varAlloc()
makeCommanderClauses(varList,commander)
return commander

def groupVarsAndMakeCommander(varList):
numVars = len(varList)
if numVars == 1:
return varList[0]
newVarList = []
numGrps = (numVars + maxSize-1) // maxSize
for i in range(numGrps):
newVarList.append(makeCommanderFromVars(varList[i*maxSize:(i+1)*maxSize]))
return groupVarsAndMakeCommander(newVarList)

print("c n queens completion SAT translation")
print("c n = ", n)
print("c num diags ruled out = ", numdiags)
print("c ")
#print("c Placed Queens:", numbers[1:])
print("c ")
print("c Variable to queen Translation")
for i in range(len(var2queen)):
print("c   ",i+1,": ",var2queen[i+1])
print("p cnf ",numSatVars," ",len(clauses))
for clause in clauses:
for lit in clause:
print(lit,end=" ")
print(0)

# do some work

generatediags_all()

print("n = ",n)
print("numdiags = ", len(outdiags))
print("diags = ", outdiags)

sys.exit(0)

processqueens()

clauses=[]

outd1=(list(filter(lambda a: len(a) > 1,  outd1)))
outd2=(list(filter(lambda a: len(a) > 1,  outd2)))

if False:
rowlens = [len(outrows[row]) for row in openrows]
collens = [len(outcols[row]) for row in opencols]
print(min(rowlens),mean(rowlens),max(rowlens))
print(min(collens),mean(collens),max(collens))
outd1=(list(filter(lambda a: len(a) > 0,  outd1)))
outd2=(list(filter(lambda a: len(a) > 0,  outd2)))
collens = [len(d1) for d1 in outd1]
print(min(collens),mean(collens),max(collens),len(collens))
collens = [len(d1) for d1 in outd2]
print(min(collens),mean(collens),max(collens),len(collens))
outd1=(list(filter(lambda a: len(a) > 1,  outd1)))
outd2=(list(filter(lambda a: len(a) > 1,  outd2)))
collens = [len(d1) for d1 in outd1]
print(min(collens),mean(collens),max(collens),len(collens))
collens = [len(d1) for d1 in outd2]
print(min(collens),mean(collens),max(collens),len(collens))

for file in outrows+outcols:
fileVar=groupVarsAndMakeCommander(file)
clauses.append([fileVar])

for diag in outd1+outd2:
groupVarsAndMakeCommander(diag)
# not maximally efficient as don't always need commander variable
# always create variable equivalent to diag but don't use that var