题解 P1219 【八皇后】
写在前面:
第一次认真写题解QwQ求赞
我初学dfs时遇到的第一题应该就是这道吧
本文面向初学者,说的较为详细
侧重于作者的做题过程
我相信很多人都会像我一样去做
枚举每一行的皇后的位置即可
对角线的处理非常令人迷茫
第一次尝试:
一开始,我兴奋地打了这样一个代码:
用二维数组vis标记一下能不能放不就好了吗?
看题解说的花里胡哨的……
void dfs(int x){
if(x==n+1){
cnt++;
if(cnt<=3){
for(int i=1;i<=n;i++)
printf("%d ",ans[i]);
printf("\n");
}
return;
}
for(int i=1;i<=n;i++){
if(!vis[x][i]){
vis[x][i]=1;
ans[x]=i;
for(int j=x+1;j<=n;j++)
vis[j][i]=1;
for(int j=1;j<=n;j++){
if(x+j>n||i+j>n) break;
vis[x+j][i+j]=1;
}
for(int j=1;j<=n;j++){
if(x+j>n||i-j<1) break;
vis[x+j][i-j]=1;
}
dfs(x+1);
for(int j=x+1;j<=n;j++)
vis[j][i]=0;
for(int j=1;j<=n;j++){
if(x+j>n||i+j>n) break;
vis[x+j][i+j]=0;
}
for(int j=1;j<=n;j++){
if(x+j>n||i-j<1) break;
vis[x+j][i-j]=0;
}
}
}
return;
}
内心无比欢悦!
——然鹅,样例都没过
原来会有重叠!回溯的时候改的并不是原来的值,原来已经否认了这种情况。
第二次尝试:
瞄了眼看起来极小的数据范围,懒人我怎么会不偷懒呢。
直接拿个三维数组viss存一下不就好了,更新还方便,无脑。
一心以为复杂度炸不了的
(我居然算都没算……)
于是这样一份代码横空出世……
void dfs(int x){
if(x==n+1){
cnt++;
if(cnt<=3){
for(int i=1;i<=n;i++)
printf("%d ",ans[i]);
printf("\n");
}
return;
}
for(int i=1;i<=n;i++){
if(!vis[x][i]){
for(int j=1;j<=n;j++)
for(int k=1;k<=n;k++)
viss[x][j][k]=vis[j][k];
vis[x][i]=1;
ans[x]=i;
for(int j=x+1;j<=n;j++)
vis[j][i]=1;
for(int j=1;j<=n;j++){
if(x+j>n||i+j>n) break;
vis[x+j][i+j]=1;
}
for(int j=1;j<=n;j++){
if(x+j>n||i-j<1) break;
vis[x+j][i-j]=1;
}
dfs(x+1);
for(int j=1;j<=n;j++)
for(int k=1;k<=n;k++)
vis[j][k]=viss[x][j][k];
}
}
return;
}
于是TLE87分滚粗
Q:那就只保存要更新的不就行了吗?这样时间复杂度不会炸的呀
A:试过了,我好像打错了。您可以自己试一试。
第三次尝试
此时我发现我一道经典题打了这么久内心已经很崩溃了
(如果在NOIPCSP考场上我就已经87分滚粗了,所以要注意数据范围啊)
仔细看题面和原来的思路,
1.我发现一行不会有两个皇后,所以这是不用存的
2.列用一维数组vis保存即可
3.对角线应该怎么存?
观察题目提供的图
向左下角的一条对角线上的格子行与列的和相等,
向右下角的一条对角线上的格子行与列的差相等
妙啊,解决了解决了。
to_left[i]表示行与列和为i的格子所在的向左下角的对角线上是否已有皇后
to_right[i]表示行与列差为i的格子所在的右下角的对角线上是否已有皇后
且慢——万一行与列的差为负值怎么办
+个n让它变成正数不就好了……
to_right[i+n]表示行与列差为i的格子所在的右下角的对角线上是否已有皇后
行与列差最大为n-1,行与列和最大为2*n
这样我们就知道这两个数组要开多大了
AC代码
#include <bits/stdc++.h>
using namespace std;
bool vis[15],to_left[30],to_right[30];
//向左的同一条对角线上和相等,向右的差相等
int n,ans[15],cnt;
void dfs(int x){
if(x==n+1){
cnt++;
if(cnt<=3){
for(int i=1;i<=n;i++)
printf("%d ",ans[i]);
printf("\n");//输出前三组解
}
return;
}
for(int i=1;i<=n;i++){
if(!vis[i]&&!to_left[x+i]&&!to_right[x-i+n]){//坐标(x,i)
vis[i]=1;to_left[x+i]=1;to_right[x-i+n]=1;//标记
ans[x]=i;
dfs(x+1);
vis[i]=0;to_left[x+i]=0;to_right[x-i+n]=0;//回溯
}
}
return;
}
int main() {
cin>>n;
dfs(1);
cout<<cnt<<endl;
return 0;
}
by cz(不进前n不改名)
QwQwQ