题目描述
检查一个如下的6 x 6的跳棋棋盘,有六个棋子被放置在棋盘上,使得每行、每列有且只有一个,每条对角线(包括两条主对角线的所有平行线)上至多有一个棋子。
上面的布局可以用序列2 4 6 1 3 5来描述,第i个数字表示在第i行的相应位置有一个棋子,如下:
行号 1 2 3 4 5 6
列号 2 4 6 1 3 5
这只是跳棋放置的一个解。请编一个程序找出所有跳棋放置的解。并把它们以上面的序列方法输出。解按字典顺序排列。请输出前3个解。最后一行是解的总个数。
//以下的话来自usaco官方,不代表洛谷观点
特别注意: 对于更大的N(棋盘大小N x N)你的程序应当改进得更有效。不要事先计算出所有解然后只输出(或是找到一个关于它的公式),这是作弊。如果你坚持作弊,那么你登陆USACO Training的帐号删除并且不能参加USACO的任何竞赛。我警告过你了!
输入输出格式
输入格式:一个数字N (6 <= N <= 13) 表示棋盘是N x N大小的。
前三行为前三个解,每个解的两个数字之间用一个空格隔开。第四行只有一个数字,表示解的总数。
输入输出样例
说明
题目翻译来自NOCOW。
USACO Training Section 1.5
分析:
之前的代码是这样写的,从1开始搜索,mape[i]表示第i行的纵坐标是几,让mape[i]=1~n,每一次赋值都用一个for循环从1循环到i-1行,判断是否有相同列的再判断是否在对角线上……结果最后一个点超时了……
超时代码:
#include<iostream> #include<cstdio> #include<algorithm> #include<cstring> #include<string> #include<queue> #include<vector> using namespace std; int n; int sum,mape[15]; int judge(int hang){ for(int i=1;i<hang;i++){ if(mape[i]==mape[hang]||hang-i==abs(mape[hang]-mape[i])) return 0; } return 1; } void dfs(int hang){ if(hang==n+1){ sum++; if(sum<=3){ printf("%d",mape[1]); for(int i=2;i<=n;i++){ printf(" %d",mape[i]); } printf("\n"); } return ; } for(int i=1;i<=n;i++){ mape[hang]=i; if(judge(hang)){ dfs(hang+1); } } } int main(){ scanf("%d",&n); sum=0; dfs(1); printf("%d\n",sum); }
后来在网上学了一下,我们可以设4个数组:
a[i]表示第i行的纵坐标为多少
b[i]表示第i列有没有被占用,1占用,0,没占用
c[i]记录左下到右上的对角线:我们可以发现,如果两个点都在左下到右上的线上,那么他们的横、纵坐标相加的值相等
d[i]记录右下到左上的对角线:我们可以发现,如果两个点都在右下到左上的线上,那么他们的横、纵坐标相减的值相等,但有可能是负数,所以我们偏移n
这样设4个数组,我们每次判断即可,不用再循环一次了。
AC代码:
#include<iostream> #include<cstdio> #include<algorithm> #include<cstring> #include<string> #include<queue> #include<vector> using namespace std; int n; const int N=33; int sum,a[N],b[N],c[N],d[N]; void dfs(int hang){ if(hang==n+1){ sum++; if(sum<=3){ printf("%d",a[1]); for(int i=2;i<=n;i++){ printf(" %d",a[i]); } printf("\n"); } return ; } for(int j=1;j<=n;j++){ if(!b[j]&&!c[hang+j]&&!d[hang-j+n]){ a[hang]=j; b[j]=1; c[hang+j]=1; d[hang-j+n]=1; dfs(hang+1); b[j]=0; c[hang+j]=0; d[hang-j+n]=0; } } } int main(){ scanf("%d",&n); sum=0; dfs(1); printf("%d\n",sum); }