\datethis \def\sqprod{\setbox0=\hbox{\kern-.13em$\times$\kern-.13em} \dimen0=\ht0 \advance\dimen0 -.09em \ht0=\dimen0 \dimen0=\dp0 \advance\dimen0 -.09em \dp0=\dimen0 \mathbin{\vcenter{\hrule\kern-.4pt \hbox{\vrule\kern-.4pt\phantom{$\box0$}\kern-.4pt\vrule}\kern-.4pt \hrule}}} @*Intro. This program finds the maximum $m$ such that $P_4\sqprod P_m$ is an induced subgraph of the $n\times n$ knight graph~$N_n$, when the path $P_4$ has been embedded in a particular way. I've tried to make it fairly fast, because this problem gets difficult rapidly as $n$ increases. Note: Please read and understand {\mc BACK-KNIGHTGRID3} before trying to read further! The simpler case handled there will make it fairly easy for you to see how the method was extended. Conversely, you might find {\mc BACK-KNIGHTGRID4} incomprehensible if you tackle it first. A knight path $(q_1,q_2,\ldots,q_m)$ is a sequence of pairs $q_k=(a_k,b_k)$ such that $(a_{k+1}-a_k)^2+(b_{k+1}-b_k)^2=5$ for $1\le k>8) @d bpart(q) ((q)&0xff) @c #include #include int n,beta,gamma; /* command-line arguments */ int mu[8]={pack(-2,1),pack(-1,-2),pack(-1,2),pack(1,-2), pack(1,2),pack(2,-1),pack(2,1),pack(-2,-1)}; int a[8]={-2,-1,-1,1,1,2,2,-2}; int b[8]={1,-2,2,-2,2,-1,1,-1}; int orth[8][2][2]={ {{1,0},{0,-1}}, {{0,1},{1,0}}, {{0,-1},{1,0}}, {{0,1},{-1,0}}, {{0,-1},{-1,0}}, {{-1,0},{0,1}}, {{-1,0},{0,-1}}, {{1,0},{0,1}}}; int mmu[8]; /* $\mu_{mmu[k]}=M_\gamma\mu_k$ */ int tr[8]; /* moves to try, followed by |-1| */ int occ[pack(maxn+maxn+2,maxn+maxn+2)]; typedef struct { int move; /* code for current choice for $q_k-q_{k-1}$ */ int inx; /* current position in the |try| list at level |k| */ int maxa; /* maximum |a| so far */ int mina; /* minimum |a| so far */ int maxb; /* maximum |b| so far */ int minb; /* minimum |b| so far */ int pad6,pad7; /* padding to fill eight |int| per |struct| */ } state; state st[maxm]; unsigned long long nodes; int corra,corrb; /* corrections used in printout */ main (int argc,char*argv[]) { register int i,j,k,m,q,qp,x,mxa,mna,mxb,mnb,athresh,bthresh,alfbet,alfbetgam; @; @; @; printf("Max m is %d (%lld nodes).\n", m,nodes+1); } @ @= if (argc!=3 || sscanf(argv[1],"%d", &n)!=1) { fprintf(stderr,"Usage: %s n betagamma\n", argv[0]); exit(-1); } if (n<4 || n>maxn) { fprintf(stderr,"Sorry, n must be between 4 and %d!\n", maxn); exit(-2); } beta=argv[2][0]-'0'; gamma=argv[2][1]-'0'; if (argv[2][2] || beta<0 || beta>7 || gamma<0 || gamma>7) { fprintf(stderr,"The beta and gamma codes must be 0, 1, 2, 3, 4, 5, 6, or 7!\n"); exit(-3); } printf("4xm knight grids strictly in a %dx%d square, case %s\n", n,n,argv[2]); @ We can improve on the crude analysis given above by looking further under the hood. The search begins by occupying cells $q_1$, $q_1'$, $q_1''$, and $q_1'''$, where $q_1'=q_1+\alpha$, $q_1''=q_1'+\beta$, and $q_1'''=q_1''+\gamma$. It's clear that the $\delta$ in subsequent steps can never be $\beta$ or $-\beta$ or $\gamma$ or $-\gamma$, because that would occupy a cell more than once. Thus at most {\it three} possibilities for~$\delta$ will arise, instead of~five, unless $\beta=\gamma=\alpha$. In fact, as discussed below, there will be at most {\it one\/} possibility for $\delta$ when $\beta\ne\pm\gamma$; and that covers most of the cases. One interesting aspect of this problem is the fact that symmetry sometimes allows us to save half of the work. More precisely, every solution has a {\it dual}, and we needn't calculate both a solution and its dual when they differ. The dual comes about because there's a symmetry of the board that takes $q_1'''$ into $q_1$ and $q_1''$ into~$q_1'$. This symmetry is a linear transformation~$\tau(q)$ that has the form $\tau(q)=Mq+\hbox{constant}$, where $M$ is a $2\times2$ orthogonal matrix that takes $\gamma$ into~$-\alpha$. (Those matrices are in fact listed in the definition of |orth| above.) If $(q_1,\ldots,q_m)$ is any solution for~$\beta$ and $\gamma$, then $\bigl(\tau(q_1'''),\ldots,\tau(q_m''')\bigr)$ is a solution for $\beta'$ and~$\gamma'$, where $\beta'=M(-\beta)$ and $\gamma'=M(-\alpha)$. It turns out that those formulas give $\gamma'=\gamma$ in most cases. The only exceptions are that $\mu_2'=\mu_3$ and $\mu_3'=\mu_2$. For example, if $\gamma=\mu_1=(-1,-2)$, we have $M=|orth[1]|= \bigl({0\,1\atop1\,0}\bigr)$. Consequently $\beta'\ne\beta$, and the dual problem is different. Indeed, problems \.{01} and \.{21} are dual, as are problems \.{11} and \.{61}, \.{31} and \.{51}; so we need do only three of those six. (We'll see below that problems \.{41} and \.{71} are illegal.) Looking further at problem \.{01}, say, notice that the first step must either make $q_2=q_1+\mu_2$ or $q_2=q_1+\mu_3$, because any moves by $\pm\beta$ or $\pm\gamma$ would cause an overlap. Furthermore, if $q_2=q_1+\mu_2$ then $q_3$ is forced to be $q_2+\mu_2$, because $\mu_3=-\mu_2$. And all subsequent moves are forced too. The maximum $m$ will therefore be less than~$n$. The same straight-line scenario occurs in reverse when $q_1=q_1+\mu_3$, with the same maximum~$m$. Hence problem~\.{01} is quite trivial---and so is its dual, problem~\.{21}, whose moves are by $\mu_0$ or $\mu_5$. Problems \.{00}, \.{11}, \.{22}, \.{33}, \.{44}, and \.{55} are not trivial in this way. But we can ignore them by working only on their duals, which are \.{60}, \.{61}, \.{63}, \.{62}, \.{64}, and \.{65}. (Notice by the way that the dual of \.{22} is not \.{62}!) Problems with $\gamma=\alpha$ (codes \.{*6}) are self-dual. In case \.{06}, a first move by $\mu_1$ is dual to a first move by~$\mu_4$; a first move by $\mu_2$ is dual to a first move by~$\mu_3$; so we save half of those cases, as we did for case~\.0 in {\mc BACK-KNIGHTGRID2}. Problems are illegal when $\beta=-\alpha$ or $\beta=-\gamma$; that rules out cases \.{7*}, \.{05}, \.{14}, \.{23}, \.{32}, \.{41}, \.{50}, and \.{67}. Furthermore, the induced-subgraph constraint rules out all cases with $\gamma=-\alpha$. It can be shown that no further conditions on $\beta$ and $\gamma$ are needed, based on the fact that the sum of four knight moves cannot be zero unless those moves cancel in pairs. @ @= { register int betap,gammap; if (beta==7 || gamma==7 || (((beta+gamma)&7)==5)) { /* $\beta=-\alpha$ or $\gamma=-\alpha$ or $\beta=-\gamma$ */ printf("This case is illegal!\n"); exit(-6); } @; j=0,betap=mmu[(5-beta)&7],gammap=mmu[7]; if ((betap!=beta)||(gammap!=gamma)) fprintf(stderr,"This problem is dual to case %d%d.\n", betap,gammap); else { fprintf(stderr,"Moves avoided on first step:"); for (k=0;k<6;k++) if (k!=beta && k!=5-beta && mmu[k]>k) { tr[j++]=k; fprintf(stderr," %d", k); } fprintf(stderr,"\n"); } x=j; /* this value is used in step |b1| below */ fprintf(stderr,"Moves tried at each step:"); for (k=0;k<6;k++) if (k!=beta && k!=5-beta && (beta!=betap || gamma!=gammap || mmu[k]<=k)) { tr[j++]=k; fprintf(stderr," %d", k); } fprintf(stderr,"\n"); tr[j]=-1; } @ @= { register aa,bb; for (k=0;k<8;k++) { aa=orth[gamma][0][0]*a[k]+orth[gamma][0][1]*b[k]; bb=orth[gamma][1][0]*a[k]+orth[gamma][1][1]*b[k]; for (j=0;;j++) if (a[j]==aa && b[j]==bb) break; mmu[k]=j; } } @ Our problem is a straightforward example of backtracking, so we can follow the outline of Algorithm 7.2.2B. At level~|k|, |q| is the (packed) value of $q_k$, and we remember the largest and smallest coordinates seen at previous levels. We can assume that $1= b1:@+@; alfbet=pack(2+a[beta],1+b[beta]); alfbetgam=pack(2+a[beta]+a[gamma],1+b[beta]+b[gamma]); q=pack(n+1,n+1); st[1].maxa=st[1].mina=st[1].maxb=st[1].minb=mxa=mna=mxb=mnb=n+1; k=1,m=1; /* the value of |x| was initialized above */ goto b3; b2pre:@+@; q=qp; b2: nodes++; if (k>m) @; x=0; b3:@+switch (tr[x]) { case 0:@+@; break; case 1:@+@; break; case 2:@+@; break; case 3:@+@; break; case 4:@+@; break; case 5:@+@; break; } b4:@+if (tr[++x]>=0) goto b3; b5:@; @ The solution we are building will fit into an $n\times n$ grid if and only |mxa-mna<=athresh| and |mxb-mnb<=bthresh|. The thresholds therefore depend on the ``spans'' of the knight path $\bigl((0,0),\alpha,\alpha+\beta\bigr)$ in each coordinate. @= { register mx,mn; mx=(a[beta]<=0? 2: 2+a[beta]); /* max(0,2,|2+a[beta]|) */ mn=0; /* min(0,2,|2+a[beta]|) */ if (2+a[beta]+a[gamma]>mx) mx=2+a[beta]+a[gamma]; if (2+a[beta]+a[gamma]mx) mx=1+b[beta]+b[gamma]; if (1+b[beta]+b[gamma]=%d!\n", n-(athresh= k--; x=st[k].inx; q-=mu[st[k].move]; @; mxa=st[k].maxa,mna=st[k].mina,mxb=st[k].maxb,mnb=st[k].minb; if (k) goto b4; @ A good optimizing compiler will probably untangle these tedious cases as well as~I have, or better. But I don't want to take chances in this inner-loop code, so I've written everything out in gory detail. The main concerns are that (i)~I don't want to test a rarely changing condition if I know that it hasn't changed; (ii)~I don't want to change a register until I know that it ought to change. @= qp=q+pack(-2,1); if (occ[qp]) goto bad0; if (occ[qp+pack(2,1)]) goto bad0; if (occ[qp+alfbet]) goto bad0; if (occ[qp+alfbetgam]) goto bad0; if (apart(qp)athresh) goto bad0; if (bpart(qp)>mxb) { st[k+1].maxb=bpart(qp); if (bpart(qp)-mnb>bthresh) goto bad0; mxb=bpart(qp),mna=apart(qp); }@+else st[k+1].maxb=mxb,mna=apart(qp); }@+else if (bpart(qp)>mxb) { st[k+1].maxb=bpart(qp); if (bpart(qp)-mnb>bthresh) goto bad0; mxb=bpart(qp),st[k+1].mina=mna; }@+else st[k+1].maxb=mxb,st[k+1].mina=mna; st[k+1].maxa=mxa,st[k+1].minb=mnb; st[k].move=0,st[k].inx=x,k++; goto b2pre; bad0:@; @ @= qp=q+pack(-1,-2); if (occ[qp]) goto bad1; if (occ[qp+pack(2,1)]) goto bad1; if (occ[qp+alfbet]) goto bad1; if (occ[qp+alfbetgam]) goto bad1; if (apart(qp)athresh) goto bad1; if (bpart(qp)bthresh) goto bad1; mnb=bpart(qp),mna=apart(qp); }@+else st[k+1].minb=mnb,mna=apart(qp); }@+else if (bpart(qp)bthresh) goto bad1; mnb=bpart(qp),st[k+1].mina=mna; }@+else st[k+1].minb=mnb,st[k+1].mina=mna; st[k+1].maxa=mxa,st[k+1].maxb=mxb; st[k].move=1,st[k].inx=x,k++; goto b2pre; bad1:@; @ @= qp=q+pack(-1,2); if (occ[qp]) goto bad2; if (occ[qp+pack(2,1)]) goto bad2; if (occ[qp+alfbet]) goto bad2; if (occ[qp+alfbetgam]) goto bad2; if (apart(qp)athresh) goto bad2; if (bpart(qp)>mxb) { st[k+1].maxb=bpart(qp); if (bpart(qp)-mnb>bthresh) goto bad2; mxb=bpart(qp),mna=apart(qp); }@+else st[k+1].maxb=mxb,mna=apart(qp); }@+else if (bpart(qp)>mxb) { st[k+1].maxb=bpart(qp); if (bpart(qp)-mnb>bthresh) goto bad2; mxb=bpart(qp),st[k+1].mina=mna; }@+else st[k+1].maxb=mxb,st[k+1].mina=mna; st[k+1].maxa=mxa,st[k+1].minb=mnb; st[k].move=2,st[k].inx=x,k++; goto b2pre; bad2:@; @ @= qp=q+pack(1,-2); if (occ[qp]) goto bad3; if (occ[qp+pack(2,1)]) goto bad3; if (occ[qp+alfbet]) goto bad3; if (occ[qp+alfbetgam]) goto bad3; if (apart(qp)>mxa) { st[k+1].maxa=apart(qp); if (apart(qp)-mna>athresh) goto bad3; if (bpart(qp)bthresh) goto bad3; mnb=bpart(qp),mxa=apart(qp); }@+else st[k+1].minb=mnb,mxa=apart(qp); }@+else if (bpart(qp)bthresh) goto bad3; mnb=bpart(qp),st[k+1].maxa=mxa; }@+else st[k+1].minb=mnb,st[k+1].maxa=mxa; st[k+1].mina=mna,st[k+1].maxb=mxb; st[k].move=3,st[k].inx=x,k++; goto b2pre; bad3:@; @ @= qp=q+pack(1,2); if (occ[qp]) goto bad4; if (occ[qp+pack(2,1)]) goto bad4; if (occ[qp+alfbet]) goto bad4; if (occ[qp+alfbetgam]) goto bad4; if (apart(qp)>mxa) { st[k+1].maxa=apart(qp); if (apart(qp)-mna>athresh) goto bad4; if (bpart(qp)>mxb) { st[k+1].maxb=bpart(qp); if (bpart(qp)-mnb>bthresh) goto bad4; mxb=bpart(qp),mxa=apart(qp); }@+else st[k+1].maxb=mxb,mxa=apart(qp); }@+else if (bpart(qp)>mxb) { st[k+1].maxb=bpart(qp); if (bpart(qp)-mnb>bthresh) goto bad4; mxb=bpart(qp),st[k+1].maxa=mxa; }@+else st[k+1].maxb=mxb,st[k+1].maxa=mxa; st[k+1].mina=mna,st[k+1].minb=mnb; st[k].move=4,st[k].inx=x,k++; goto b2pre; bad4:@; @ @= qp=q+pack(2,-1); if (occ[qp]) goto bad5; if (occ[qp+pack(2,1)]) goto bad5; if (occ[qp+alfbet]) goto bad5; if (occ[qp+alfbetgam]) goto bad5; if (apart(qp)>mxa) { st[k+1].maxa=apart(qp); if (apart(qp)-mna>athresh) goto bad5; if (bpart(qp)bthresh) goto bad5; mnb=bpart(qp),mxa=apart(qp); }@+else st[k+1].minb=mnb,mxa=apart(qp); }@+else if (bpart(qp)bthresh) goto bad5; mnb=bpart(qp),st[k+1].maxa=mxa; }@+else st[k+1].minb=mnb,st[k+1].maxa=mxa; st[k+1].mina=mna,st[k+1].maxb=mxb; st[k].move=5,st[k].inx=x,k++; goto b2pre; bad5:@; @ @= occ[q+pack(+2,+1)]++; occ[q+pack(+2,-1)]++; occ[q+pack(+1,+2)]++; occ[q+pack(+1,-2)]++; occ[q+pack(-1,+2)]++; occ[q+pack(-1,-2)]++; occ[q+pack(-2,+1)]++; occ[q+pack(-2,-1)]++; occ[q+pack(+4,+2)]++; occ[q+pack(+4,+0)]++; occ[q+pack(+3,+3)]++; occ[q+pack(+3,-1)]++; occ[q+pack(+1,+3)]++; occ[q+pack(+1,-1)]++; occ[q+pack(+0,+2)]++; occ[q+pack(+0,+0)]++; occ[q+pack(+2,+1)+alfbet]++; occ[q+pack(+2,-1)+alfbet]++; occ[q+pack(+1,+2)+alfbet]++; occ[q+pack(+1,-2)+alfbet]++; occ[q+pack(-1,+2)+alfbet]++; occ[q+pack(-1,-2)+alfbet]++; occ[q+pack(-2,+1)+alfbet]++; occ[q+pack(-2,-1)+alfbet]++; occ[q+pack(+2,+1)+alfbetgam]++; occ[q+pack(+2,-1)+alfbetgam]++; occ[q+pack(+1,+2)+alfbetgam]++; occ[q+pack(+1,-2)+alfbetgam]++; occ[q+pack(-1,+2)+alfbetgam]++; occ[q+pack(-1,-2)+alfbetgam]++; occ[q+pack(-2,+1)+alfbetgam]++; occ[q+pack(-2,-1)+alfbetgam]++; @ @= occ[q+pack(+2,+1)]--; occ[q+pack(+2,-1)]--; occ[q+pack(+1,+2)]--; occ[q+pack(+1,-2)]--; occ[q+pack(-1,+2)]--; occ[q+pack(-1,-2)]--; occ[q+pack(-2,+1)]--; occ[q+pack(-2,-1)]--; occ[q+pack(+4,+2)]--; occ[q+pack(+4,+0)]--; occ[q+pack(+3,+3)]--; occ[q+pack(+3,-1)]--; occ[q+pack(+1,+3)]--; occ[q+pack(+1,-1)]--; occ[q+pack(+0,+2)]--; occ[q+pack(+0,+0)]--; occ[q+pack(+2,+1)+alfbet]--; occ[q+pack(+2,-1)+alfbet]--; occ[q+pack(+1,+2)+alfbet]--; occ[q+pack(+1,-2)+alfbet]--; occ[q+pack(-1,+2)+alfbet]--; occ[q+pack(-1,-2)+alfbet]--; occ[q+pack(-2,+1)+alfbet]--; occ[q+pack(-2,-1)+alfbet]--; occ[q+pack(+2,+1)+alfbetgam]--; occ[q+pack(+2,-1)+alfbetgam]--; occ[q+pack(+1,+2)+alfbetgam]--; occ[q+pack(+1,-2)+alfbetgam]--; occ[q+pack(-1,+2)+alfbetgam]--; occ[q+pack(-1,-2)+alfbetgam]--; occ[q+pack(-2,+1)+alfbetgam]--; occ[q+pack(-2,-1)+alfbetgam]--; @ @= { m=k; for (qp=pack(n-mna+corra,n-mnb+corrb),k=0;k