目录
0.本题为裸题。
1.题目描述:
给定一个正整数n(),表示一个n×n的盘面。在该盘面上放上n个皇后,满足任意两个皇后之间不能互相攻击(皇后攻击范围为行、列、两条对角线)。
如图是n=6时的一个解。则答案表示为2 4 6 1 3 5(每一行的皇后在哪一列)。请输出前三种方案和总方案数。具体输入输出格式见原题。
2.思路
(1)如何确定皇后的位置
既然要在n×n的格子上放n个皇后,那么每个皇后肯定是不同行、不同列的。所以我们必
须要在每一行都放上一个皇后。
于是我们就有了这样的伪代码——
int n,cnt,ans[N];//ans是存放答案的数组
int a[N][N];
void dfs(int x){//表示要放第几列的皇后
if(x==n+1){
if(cnt<3){
for(int i=1;i<=n;i++) printf("%d ",ans[i]);
printf("\n");
}
cnt++;
return;
}
for(int i=1;i<=n;i++){
if(/*有标记*/) continue;
//打标记
ans[x]=i;
dfs(x+1);
//去标记
}
}
signed main(){
scanf("%d",&n);
dfs(1);
printf("%d",cnt);
return 0;
}
所以我们现在的问题就在于如何打标记。
(2)打标记
打标记是一门学问,去标记同理。
由题目易得,我们可以使用O(n)的时间复杂度来一个一个修改。
void change(int x,int y,int z){
for(int i=1;i<=n;i++) a[i][y]+=z;
for(int j=1;j<=n;j++) a[x][j]+=z;
for(int i=1;x-i>0&&y-i>0;i++) a[x-i][y-i]+=z;
for(int i=1;x+i<=n&&y-i>0;i++) a[x+i][y-i]+=z;
for(int i=1;x-i>0&&y+i<=n;i++) a[x-i][y+i]+=z;
for(int i=1;x+i<=n&&y+i<=n;i++) a[x+i][y+i]+=z;
a[x][y]-=z;
}
但是,你会发现你将喜提TLE。
所以,我们要仔细观察,找一些规律:
对于同一列,我们可以直接O(1)标记:定义一个bool类型的数组lie[i],表示第i列是否曾经放置过皇后。这个很好操作。那么对角线怎么办呢?其实很简单。观察一条左上-右下的对角线,当x减一时,y也减一。所以它们的差不变。而左下-右上的对角线则刚好相反,和为定值。同时我们不难发现不同的对角线和或差都不同。所以我们可以以和或者差来表示对角线了。
可是有一个问题——左上-右下的对角线的两坐标之差可能为负数。举个例子,一个3×3的矩阵,其中(1,2)是在x-y=-1的左上-右下对角线上。可是数组的下标又没有负数。于是,我们引进一个新的概念:偏移量。说起来也简单,就是加上一个数字。显然,其1差值最小为-n+1,最大为n-1。因此,我们设偏移量为n,数组的大小要开到2n。左下、右上的数组大小于是要开到2n的(小思考,结尾答案)。
3.代码
于是,我们就有了最终的代码。
#include<iostream>
#include<stdio.h>
using namespace std;
const int N=20;
bool lie[N];
bool Left[N]/*x-y为Left[i]*/,Right[N+N]/*x+y为Right[i]*/;
int ans[N];
int n;
int cnt;
void dfs(int x){//表示要放第几列的皇后
if(x==n+1){
if(cnt<3){
for(int i=1;i<=n;i++) printf("%d ",ans[i]);
printf("\n");
}
cnt++;
}
for(int i=1;i<=n;i++){//枚举:在第x行第i列
if(lie[i]||Left[x-i+N]||Right[x+i]) continue;
lie[i]=true;//标记
Left[x-i+N]=true;
Right[x+i]=true;
ans[x]=i;//记录答案
dfs(x+1);//递归
lie[i]=false;//回溯
Left[x-i+N]=false;
Right[x+i]=false;
}
}
signed main(){//简约的main函数
scanf("%d",&n);
dfs(1);
printf("%d",cnt);
return 0;
}
4.结尾答案
对于一个n×n的矩阵,若在(n,n)的左下-右上对角线,其和为2n,实际上数组的大小一定要比2n还大,因为大小的2n的数组其可访问的范围是[0,2n-1]。