#!/usr/bin/env python3
import lzma, sys, math, random, time, os

def load(p):
    cls=[]; n=0
    for line in lzma.open(p,'rt') if p.endswith('.xz') else open(p):
        if not line or line[0]=='c': continue
        if line[0]=='p': n=int(line.split()[2]); continue
        c=tuple(map(int,line.split()[:-1])); cls.append(c)
    D=len(cls[0]); T=0
    while T<len(cls) and len(cls[T])==D and all(x>0 for x in cls[T]) and cls[T]==tuple(range(cls[T][0],cls[T][0]+D)): T+=1
    i=T+T*(D*(D-1)//2)
    defs={}; idx=i
    while idx<len(cls):
        c=cls[idx]
        if len(c)==2 and c[0]<0 and -c[0]<=T*D and c[1]>T*D:
            aux=c[1]; arr=[]
            while idx<len(cls) and len(cls[idx])==2 and cls[idx][0]<0 and cls[idx][1]==aux:
                arr.append(-cls[idx][0]); idx+=1
            if idx<len(cls) and cls[idx][0]==-aux:
                idx+=1; defs[aux]=arr; continue
        break
    g=0
    for arr in defs.values():
        for a,b in zip(arr,arr[1:]): g=math.gcd(g,b-a)
    P=g//D; K=T//P; L=len(defs)//(P*D)
    locks=[]
    for chunk in range(L):
        arr=defs[min(defs)+chunk*P*D]
        locks.append(set((v-1)//(P*D) for v in arr))
    return D,P,K,locks

def seqs(D,P):
    out=[]
    def rec(s):
        if len(s)==P: out.append(tuple(s)); return
        for d in range(D):
            if not s or abs(s[-1]-d)<=1:
                s.append(d); rec(s); s.pop()
    rec([]); return out

def solve(D,P,K,locks,limit=30):
    codes=seqs(D,P); print('domain',len(codes),'K,L',K,len(locks))
    allkeys=set(range(K)); unauth=[list(allkeys-A) for A in locks]
    contains=[[] for _ in range(K)]
    for l,A in enumerate(locks):
        for k in A: contains[k].append(l)
    rng=random.Random(1)
    # init from random or membership-signature hash
    best=10**9; bestassign=None
    t0=time.time(); restarts=0
    while time.time()-t0<limit:
        restarts+=1
        assign=[rng.randrange(len(codes)) for _ in range(K)]
        # maybe seed special frequent keys with constant codes
        for step in range(200000):
            masks=[]
            viol_by_key=[0]*K; viol=[]
            for l,A in enumerate(locks):
                m=[0]*P
                for k in A:
                    c=codes[assign[k]]
                    for p,d in enumerate(c): m[p]|=1<<d
                masks.append(m)
                for k in unauth[l]:
                    c=codes[assign[k]]
                    if all(m[p]>>d &1 for p,d in enumerate(c)):
                        viol.append((l,k)); viol_by_key[k]+=1
            v=len(viol)
            if v<best:
                best=v; bestassign=assign[:]
                print('best',best,'restart',restarts,'step',step,'time',time.time()-t0, flush=True)
                if v==0: return [codes[i] for i in assign]
            if time.time()-t0>=limit: break
            # pick key in violation weighted
            if viol:
                if rng.random()<0.7:
                    k=max(range(K), key=lambda x: viol_by_key[x]+rng.random()*0.01)
                else:
                    k=rng.choice(viol)[1]
            else: break
            old=assign[k]
            sample = rng.sample(range(len(codes)), min(len(codes), 80))
            if old not in sample: sample.append(old)
            bestc=old; bestscore=v
            # full eval for candidates
            for ci in sample:
                assign[k]=ci
                vv=0
                for l,A in enumerate(locks):
                    m=[0]*P
                    for kk in A:
                        c=codes[assign[kk]]
                        for p,d in enumerate(c): m[p]|=1<<d
                    for kk in unauth[l]:
                        c=codes[assign[kk]]
                        if all(m[p]>>d &1 for p,d in enumerate(c)): vv+=1
                    if vv>bestscore: pass
                if vv<bestscore or (vv==bestscore and rng.random()<0.01):
                    bestscore=vv; bestc=ci
            assign[k]=bestc
    return None

if __name__=='__main__':
    D,P,K,locks=load(sys.argv[1])
    sol=solve(D,P,K,locks,float(sys.argv[2]) if len(sys.argv)>2 else 30)
    print('SOL', sol is not None)
    if sol: print(sol)
