import sys,lzma,os,time,random

def parse(path):
  opener=lzma.open if path.endswith('.xz') else open
  clauses=[]; blocks=[]; N=0
  with opener(path,'rt') as f:
    for line in f:
      if not line or line[0] in 'c%\n': continue
      if line[0]=='p': N=int(line.split()[2]); continue
      lits=list(map(int,line.split()[:-1])); clauses.append(lits)
      if len(lits)>2 and all(x>0 for x in lits): blocks.append(lits)
  blocks.sort(key=min); n=len(blocks); d=len(blocks[0]); full=(1<<d)-1
  mp={v:(i,a) for i,b in enumerate(blocks) for a,v in enumerate(b)}
  forb={}
  for lits in clauses:
    if len(lits)==2 and lits[0]<0 and lits[1]<0:
      a,b=-lits[0],-lits[1]
      if a not in mp or b not in mp: return None
      i,ai=mp[a]; j,bj=mp[b]
      if i==j: continue
      if i>j: i,j=j,i; ai,bj=bj,ai
      arr=forb.setdefault((i,j),[0]*d); arr[ai]|=1<<bj
  allowed={}; neigh=[set() for _ in range(n)]
  for (i,j),fb in forb.items():
    ai=[full ^ fb[a] for a in range(d)]
    allowed[(i,j)]=ai
    # reverse masks
    rev=[]
    for b in range(d):
      m=0
      for a in range(d):
        if (ai[a]>>b)&1: m|=1<<a
      rev.append(m)
    allowed[(j,i)]=rev
    neigh[i].add(j); neigh[j].add(i)
  return n,d,blocks,allowed,[list(s) for s in neigh]

def popcount(x): return x.bit_count()

def ac3(dom,allowed,neigh,queue,trail):
  while queue:
    i,j=queue.pop()
    old=dom[i]; new=old
    arr=allowed.get((i,j))
    if arr is None: continue
    m=old
    while m:
      lb=m & -m; a=lb.bit_length()-1
      if arr[a] & dom[j] == 0: new &= ~lb
      m-=lb
    if new!=old:
      if new==0: return False
      trail.append((i,old)); dom[i]=new
      for k in neigh[i]:
        if k!=j: queue.append((k,i))
  return True

def search(data,limit=60,seed=0):
  n,d,blocks,allowed,neigh=data; full=(1<<d)-1; end=time.time()+limit; rnd=random.Random(seed)
  dom=[full]*n; nodes=0
  # initial AC all arcs
  tr=[]
  if not ac3(dom,allowed,neigh,[(i,j) for i in range(n) for j in neigh[i]],tr): return None,nodes
  def rec(depth):
    nonlocal nodes
    if time.time()>end: raise TimeoutError
    # choose min domain >1, tie max degree
    v=-1; bc=999; bd=-1
    for i,m in enumerate(dom):
      c=popcount(m)
      if c>1 and (c<bc or (c==bc and len(neigh[i])>bd)):
        v=i; bc=c; bd=len(neigh[i])
    if v<0:
      return [ (dom[i]&-dom[i]).bit_length()-1 for i in range(n)]
    # least constraining values: count supports in neighbors
    vals=[]; m=dom[v]
    while m:
      lb=m & -m; a=lb.bit_length()-1; score=0
      for y in neigh[v]:
        arr=allowed.get((y,v))
        if arr: # values of y supported by v=a
          # easier: after v=a, domain y allowed from v side allowed[v,y][a]
          score += popcount(dom[y] & allowed[(v,y)][a])
      vals.append((-score, rnd.random(), a)); m-=lb
    vals.sort()
    if depth<3 and rnd.random()<0.5: rnd.shuffle(vals)
    for _,__,a in vals:
      nodes+=1
      oldtrail=[]; old=dom[v]
      if old != (1<<a):
        oldtrail.append((v,old)); dom[v]=1<<a
      q=[(y,v) for y in neigh[v]]
      if ac3(dom,allowed,neigh,q,oldtrail):
        r=rec(depth+1)
        if r is not None: return r
      for i,oldm in reversed(oldtrail): dom[i]=oldm
    return None
  try: return rec(0),nodes
  except TimeoutError: return None,nodes

if __name__=='__main__':
  for p in sys.argv[1:]:
    data=parse(p); t=time.time(); sol,nodes=search(data,20,1)
    print(os.path.basename(p), sol is not None, 't',time.time()-t,'nodes',nodes)
