#!/usr/bin/env python3
import lzma, sys, itertools, collections, time, os

def parse(path):
    op = lzma.open if path.endswith('.xz') else open
    clauses=[]; nvars=0
    with op(path,'rt') as f:
        for line in f:
            if not line.strip() or line[0]=='c': continue
            if line[0]=='p':
                nvars=int(line.split()[2]); continue
            xs=list(map(int,line.split()))
            if xs and xs[-1]==0: xs=xs[:-1]
            if xs: clauses.append(xs)
    return nvars,clauses

def detect(nvars,clauses):
    opts=[]
    for n,L in [(4,33),(5,21),(5,26)]:
      for enc,bits in [('one',n),('una',n-1),('bin',(n-1).bit_length())]:
        sc=L*bits
        if sc<nvars: opts.append((n,L,enc,bits,sc))
    units={c[0] for c in clauses if len(c)==1}
    good=[]
    for n,L,enc,bits,sc in opts:
      ass=[]
      for p in range(n):
        val=p
        if enc=='one':
          for c in range(n): ass.append((p*bits+c+1) * (1 if c==val else -1))
        elif enc=='una':
          for b in range(bits): ass.append((p*bits+b+1) * (1 if b<val else -1))
        else:
          for b in range(bits): ass.append((p*bits+b+1) * (1 if (val>>b)&1 else -1))
      if all(x in units for x in ass): good.append((n,L,enc,bits,sc))
    # Disambiguate this benchmark family's exact (n,L,encoding,tree/flat) shapes.
    table={
      852:(4,33,'one',4,132),2052:(4,33,'one',4,132),
      819:(4,33,'una',3,99),2019:(4,33,'una',3,99),
      786:(4,33,'bin',2,66),1986:(4,33,'bin',2,66),
      5590:(5,21,'one',5,105),9075:(5,21,'one',5,105),6969:(5,26,'one',5,130),11479:(5,26,'one',5,130),
      5569:(5,21,'una',4,84),9054:(5,21,'una',4,84),6943:(5,26,'una',4,104),11453:(5,26,'una',4,104),
      5548:(5,21,'bin',3,63),9033:(5,21,'bin',3,63),6917:(5,26,'bin',3,78),11427:(5,26,'bin',3,78),
    }
    if nvars in table and table[nvars] in good:
      return [table[nvars]]
    return good

def word_assumps(word,n,enc,bits):
    a=[]
    for p,val in enumerate(word):
      if enc=='one':
        for c in range(n): a.append((p*bits+c+1) * (1 if c==val else -1))
      elif enc=='una':
        for b in range(bits): a.append((p*bits+b+1) * (1 if b<val else -1))
      else:
        for b in range(bits): a.append((p*bits+b+1) * (1 if (val>>b)&1 else -1))
    return a

def known_words(n,L):
    if n==4:
      s='012301203120132010231021302103210'
      yield [int(c) for c in s]
      # generate more shortest superstrings starting with 0123
    else:
      # generated greedily over permutation-overlap graph; hardcoded stable seeds
      seeds=[
        '01234012304123014230124301',
        '0123401231042310243103214032140321043210',
        '0123401231042310243102134021304213042130',
        '0123401231042310243102134021430214302134',
        '0123401231042310243102134012304123041230',
      ]
      for s in seeds:
        if len(s)>=L: yield [int(c) for c in s[:L]]

def greedy_words(n,L,limit=200):
    perms=[''.join(map(str,p)) for p in itertools.permutations(range(n))]
    start=''.join(map(str,range(n)))
    def ov(a,b):
      for k in range(n,0,-1):
        if a[-k:]==b[:k]: return k
      return 0
    import random
    rnd=random.Random(1)
    for t in range(limit):
      s=start; seen={start}
      while len(s)<L:
        last=s[-n:]
        cand=[]
        for p in perms:
          if p in seen: continue
          k=ov(s,p)
          add=n-k
          if len(s)+add<=L: cand.append((add, rnd.random(), p, k))
        if not cand: break
        cand.sort()
        # vary among near-best
        m=min(c[0] for c in cand)
        top=[c for c in cand if c[0]<=m+(1 if rnd.random()<0.25 else 0)]
        add,_,p,k=rnd.choice(top[:min(len(top),8)])
        s+=p[k:]; seen.add(p)
      if len(s)<L: s += start[:L-len(s)]
      yield [int(c) for c in s[:L]]

class Solver:
  def __init__(self,nvars,clauses):
    self.n=nvars; self.clauses=clauses
    self.pos=[[] for _ in range(nvars+1)]; self.neg=[[] for _ in range(nvars+1)]
    for i,c in enumerate(clauses):
      for lit in c:
        (self.pos if lit>0 else self.neg)[abs(lit)].append(i)
  def solve(self,assumps,maxtime=10):
    deadline=time.time()+maxtime
    val=[0]*(self.n+1); trail=[]
    q=collections.deque()
    def assign(lit):
      v=abs(lit); s=1 if lit>0 else -1
      if val[v]: return val[v]==s
      val[v]=s; trail.append(v); q.append(lit); return True
    for lit in assumps:
      if not assign(lit): return None
    # naive propagation scan clauses, enough for prototype
    def prop():
      changed=True
      while changed:
        if time.time()>deadline: raise TimeoutError
        changed=False
        for c in self.clauses:
          sat=False; un=0; last=0
          for lit in c:
            vv=val[abs(lit)]
            if vv and vv==(1 if lit>0 else -1): sat=True; break
            if vv==0: un+=1; last=lit
          if sat: continue
          if un==0: return False
          if un==1:
            if not assign(last): return False
            changed=True
      return True
    def choose():
      best=0; bd=-1
      for v in range(1,self.n+1):
        if val[v]==0:
          d=len(self.pos[v])+len(self.neg[v])
          if d>bd: bd=d; best=v
      return best
    sys.setrecursionlimit(50000)
    def dfs():
      if not prop(): return False
      v=choose()
      if v==0: return True
      mark=len(trail)
      for lit in (v,-v):
        if assign(lit) and dfs(): return True
        while len(trail)>mark:
          val[trail.pop()]=0
        q.clear()
      return False
    try:
      ok=dfs()
    except TimeoutError:
      return None
    if not ok: return None
    return val

def forced_false_from_string(clauses, sc, assumps):
  aval={abs(x):(1 if x>0 else -1) for x in assumps if abs(x)<=sc}
  out=set()
  for c in clauses:
    if len(c)==2:
      a,b=c
      for y,lit in ((a,b),(b,a)):
        if y<0 and abs(lit)<=sc and abs(y)>sc:
          vv=aval.get(abs(lit),0)
          if vv and vv != (1 if lit>0 else -1):
            out.add(-abs(y))
  return list(out)

def count_perms(w,n):
  return len({tuple(w[i:i+n]) for i in range(len(w)-n+1) if len(set(w[i:i+n]))==n})

if __name__=='__main__':
  for path in sys.argv[1:]:
    nv,cs=parse(path); det=detect(nv,cs); print(os.path.basename(path),nv,len(cs),det)
    if not det: continue
    n,L,enc,bits,sc=det[0]
    S=Solver(nv,cs)
    tried=0
    for w in list(known_words(n,L))+list(greedy_words(n,L,80)):
      tried+=1
      print(' try', ''.join(map(str,w)), 'perms', count_perms(w,n), flush=True)
      ass=word_assumps(w,n,enc,bits)
      ass += forced_false_from_string(cs, sc, ass)
      model=S.solve(ass, maxtime=5)
      if model:
        print(' SAT word', ''.join(map(str,w)), 'tried', tried); break
    else: print(' none', tried)
