经典问题:八皇后

目录

0.本题为裸题。

1.题目描述:

2.思路

             (1)如何确定皇后的位置

    (2)打标记

3.代码

4.结尾答案


0.本题为裸题。

1.题目描述:

        给定一个正整数n(6\leq n\leq 13),表示一个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]。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值