题目描述
大小为3的棋盘游戏里有3个白色棋子,3个黑色棋子,和一个有7个格子一线排开的木盒子。3个白棋子被放在一头,3个黑棋子被放在另一头,中间的格子空着。
初始状态: WWW_BBB
目标状态: BBB_WWW
在这个游戏里有两种移动方法是允许的:
你可以把一个棋子移到与它相邻的空格;
你可以把一个棋子跳过一个(仅一个)与它不同色的棋子到达空格。
大小为N的棋盘游戏包括N个白棋子,N个黑棋子,还有有2N+1个格子的木盒子。
这里是3-棋盘游戏的解,包括初始状态,中间状态和目标状态:
WWW BBB
WW WBBB
WWBW BB
WWBWB B
WWB BWB
W BWBWB
WBWBWB
BW WBWB
BWBW WB
BWBWBW
BWBWB W
BWB BWW
B BWBWW
BB WBWW
BBBW WW
BBB WWW
请编一个程序解大小为N的棋盘游戏(1 <= N <= 12)。要求用最少的移动步数实现。
INPUT FORMAT
一个整数N。
SAMPLE INPUT (file shuttle.in)
3
OUTPUT FORMAT
用空格在棋盘的位置(位置从左到右依次为1, 2, …, 2N+1)表示棋盘的状态。输出棋盘的状态变换序列,每行20个数(除了最后一行)。
输出的解还应当有最小的字典顺序(即如果有多组移动步数最小的解,输出第一个数最小的解;如果还有多组,输出第二个数最小的解;…)。
SAMPLE OUTPUT (file shuttle.out)
3 5 6 4 2 1 3 5 7 6 4 2 3 5 4
看到只有n<=12,第一个反应就是暴搜加剪枝。
想到的第一个剪枝是状态压缩剪枝,然后算了一下,我们需要开2^25×25=838860800那么大。
毫无疑问,行不通。
有一个神奇剪枝就是A的只往右边换,B的只往左边换,效率超级高。
注意答案的个数一定是n×(n+2),所以层数超过n×(n+2)时就退,等于时就判断是否正确。
判断时可以用我们之前提到的状态压缩,二进制中把A看作1,其余的看作0。
代码:
#include
#define fo(i,x,y) for(int i=x;i<=y;i++)
using namespace std;
int n,a[25],b[169],p,max,ans;
void dg(int x,int w,int s)
{
if(x>max)return;
b[x]=w;
if(x==max)
{
if (w!=n+1) return;
fo(i,1,x)
{
printf("%d ",b[i]);
if(i%20==0) printf("\n");
}
p=1;
return;
}
if(w>2&&a[w-2]==1)
{
int k=a[w];a[w]=a[w-2];a[w-2]=k;
dg(x+1,w-2,s-(1<<(w-3))+(1<<(w-1)));
if(p)return;
k=a[w];a[w]=a[w-2];a[w-2]=k;
}
if(w>1&&a[w-1]==1)
{
int k=a[w];a[w]=a[w-1];a[w-1]=k;
dg(x+1,w-1,s-(1<<(w-2))+(1<<(w-1)));
if(p)return;
k=a[w];a[w]=a[w-1];a[w-1]=k;
}
if(w1&&a[w+1]==2)
{
int k=a[w];a[w]=a[w+1];a[w+1]=k;
dg(x+1,w+1,s);
if(p)return;
k=a[w];a[w]=a[w+1];a[w+1]=k;
}
if(w2]==2)
{
int k=a[w];a[w]=a[w+2];a[w+2]=k;
dg(x+1,w+2,s);
if(p)return;
k=a[w];a[w]=a[w+2];a[w+2]=k;
}
}
int main()
{
scanf("%d",&n);
fo(i,1,n) {a[i]=1; a[i+n+1]=2;}
a[n+1]=0;
max=(n+2)*n;
ans=(1<<(n+n+1))-(1<<(n+1));
dg(0,n+1,(1<1);
}
其实我一开始的做法并不是暴搜,因为我没有想到那个神奇的剪枝。
经LL推荐,我用较慢的程序打了个表,找规律。
下面是n=1..6的情况:
n=1
1 3 2
n=2
2 4 5 3 1 2 4 3
n=3
3 5 6 4 2 1 3 5 7 6 4 2 3 5 4
n=4
4 6 7 5 3 2 4 6 8 9 7 5 3 1 2 4 6 8 7 5 3 4 6 5
n=5
5 7 8 6 4 3 5 7 9 10 8 6 4 2 1 3 5 7 9 11 10 8 6 4 2 3 5 7 9 8 6 4 5 7 6
n=6
6 8 9 7 5 4 6 8 10 11 9 7 5 3 2 4 6 8 10 12 13 11 9 7 5 3 1 2 4 6 8 10 12 11 9 7 5 3 4 6 8 10 9 7 5 6 8 7
我们发现第一个答案一定是n。
然后我们把前后两个数相减(a[i+1]-a[i]),可以得到n×(n+2)-1个差。
n=1
2 -1
n=2
2 1 -2 -2 1 2 -1
n=3
2 1 -2 -2 -1 2 2 2 -1 -2 -2 1 2 -1
n=4
2 1 -2 -2 -1 2 2 2 1 -2 -2 -2 -2 1 2 2 2 -1 -2 -2 1 2 -1
n=5
2 1 -2 -2 -1 2 2 2 1 -2 -2 -2 -2 -1 2 2 2 2 2 -1 -2 -2 -2 -2 1 2 2 2 -1 -2 -2 1 2 -1
n=6
2 1 -2 -2 -1 2 2 2 1 -2 -2 -2 -2 -1 2 2 2 2 2 1 -2 -2 -2 -2 -2 -2 1 2 2 2 2 2 -1 -2 -2 -2 -2 1 2 2 2 -1 -2 -2 1 2 -1
最后一个数都是-1,把它独立出来。
前面是1个2,1个1,2个-2,1个-1,3个2,一个1,……,n-1个(-1)^n×2,1个(n-1)^(n)。
之后n个(-1)^(n-1)×2,不算它们,把前面出现过的数倒过来,就是接下来的数,最后一个是-1。
非常愉快的从n开始,加上这些差,输出就行了。
代码:
#include
#define fo(i,x,y) for(int i=x;i<=y;i++)
using namespace std;
int n,b[169],l,r;
int main()
{
scanf("%d",&n);
l=2;r=1;
while(r1,r)
{
b[++b[0]]=l;
b[n*(n+2)-b[0]-1]=b[b[0]];
}
b[++b[0]]=l/2;
b[n*(n+2)-b[0]-1]=b[b[0]];
l=-l; r++;
}
fo(i,1,r)
b[++b[0]]=l;
b[n*(n+2)-1]=-1;
l=n;
fo(i,1,n*(n+2))
{
printf("%d ",l);
l+=b[i];
if (i%20==0)
printf("\n");
}
}