算法设计与分析——n后问题(回溯法+位运算)
一、问题描述
在n×n格的国际象棋上摆放n个皇后,使其不能互相攻击,即任意两个皇后都不能处于同一行、同一列或同一斜线上,问有多少种摆法。

二、算法设计


解n后问题的回溯算法描述如下:
#include <iostream>
#include <cstdio>
#include <cmath>
#include <algorithm>
using namespace std;
int n;
long long int sum;
int x[11];
int Check(int row, int col)
{
for(int i = 1; i < row; i++)
{
if(col == x[i] || abs(row - i) ==abs(col - x[i])) //在同一列或者在同一斜线。一定不在同一行
return 0;
}
return 1;
} void backtrack(int k)
{
if(k>n) //求出一种解, sum+1
{
sum++;
return;
}
for(int i=1; i<=n; i++)//n叉树
{
if(Check(k, i)) //剪枝,检查是否满足条件
{
x[k]=i; //记录第k皇后在第i列
backtrack(k+1); //递归查找
}
} }
int main()
{
while(scanf("%d",&n)!=EOF)
{
if(n==0)
{
break;
}
for(int i=0; i<n; i++)
{
x[i]=0;
}
sum=0;
backtrack(1);
printf("%lld\n",sum);
}
return 0;
}
三、位运算优化
上面的程序我在求16皇后的时候大概跑了近乎200s,我们可以想象到每次搜索第k行的状态的时候,都是从第1列开始枚举每一列,这样是很低效的,浪费了很多时间,我们需要提高枚举的命中率甚至每一次的尝试都是正确的,都是可行解。
那么该怎么做?
其实n皇后的搜索规模并不是很大,在目前的需求中,最多不过20位,我们可以使用二进制来表示一个集合,而一旦使用二进制时,集合的交并补运算就可以直接使用位运算来实现了,我们知道位运算在计算机中是相当快的(使用指令少)。安利一篇我之前做过的位运算的实验https://www.cnblogs.com/wkfvawl/p/10034576.html
两个数字与运算就是求交集,或运算就是求并集,取反就是求集合的补集。
我们先来看程序代码:
void test(int row, int ld, int rd)
{
int pos, p;
if ( row != upperlim )
{
pos = upperlim & (~(row | ld | rd ));
while ( pos )
{
p = pos & (~pos + );
pos = pos - p;
test(row | p, (ld | p) << , (rd | p) >> );
}
}
else
++Ans;
}
初始化:
upperlim = ( << n)-; Ans = ;
upperlime =(1 << n)-1 就生成了n个1组成的二进制数。
程序从上到下搜索。
这样我们使用三个参数row、ld和rd,分别表示在纵列和两个对角线方向的限制条件下这一行的哪些地方不能放。位于该行上的不能放置的位置就用row、ld和rd中的1来表示。把它们三个并起来,得到该行所有的禁位,取反后就得到所有可以放的位置(用pos来表示)
这里需要注意一点:
对应row、ld和rd来说1表示的是不能放置皇后的占用位置,但对于pos来说1代表可以放置皇后的位置!
p = pos & (~pos + 1)其结果是取出最右边的那个1。
因为取反以后刚好所有数都是相反,再加 1 ,就是改变最低位,如果低位的几个数都是1,加的这个 1 就会进上去,一直进到 0 ,在做与运算就和原数对应的 1 重合了。举例可以说明:
原数 0 0 0 0 1 0 0 0 原数 0 1 0 1 0 0 1 1
取反 1 1 1 1 0 1 1 1 取反 1 0 1 0 1 1 0 0
加1 1 1 1 1 1 0 0 0 加1 1 0 1 0 1 1 0 1
与运算 0 0 0 0 1 0 0 0 and 0 0 0 0 0 0 0 1
从集合的角度来看p是位置集合pos上的一位置,将皇后置于位置p,位置集合就要减少一个位置,所以需要:
pos = pos - p
那这个while我们也就明白了,需要把位置集合全都用完放置皇后嘛!
最后我们要注意递归调用时三个参数的变化,每个参数都加上了一个占位,但两个对角线方向的占位对下一行的影响需要平移一位。最后,如果递归到某个时候发现row=upperlim了,说明n个皇后全放进去了,找到的解的个数加1。


这里拿两个例子来说明,对于第一张图的例子。
在已经安置好3个皇后的情况下,对于第4个皇后
row = 101010 棕色线代表纵列上不能放置皇后的占位
ld = 100100 蓝色线代表左对角线列上不能放置皇后的占位
rd = 000111 绿色线代表右对角线列上不能放置皇后的占位

对角线是45度倾斜的,这样两个对角线方向的占位要影响下一行对应位置的下一位也就很好理解了,这恰恰可以使用位运算的左移和右移来实现。
(ld | p)<< 1 是因为由ld造成的占位在下一行要右移一下;
(rd | p)>> 1 是因为由rd造成的占位在下一行要左移一下。
当然 ld rd row 还要和upperlime 与运算 一下,这样做的结果就是从最低位数起取n个数为有效位置,原因是在上一次的运算中ld发生了右移,如果不and的话,就会误把n以外的位置当做有效位。
#include<cstdio>
#include<algorithm>
#define ll long long int
using namespace std; // sum用来记录皇后放置成功的不同布局数;upperlim用来标记所有列都已经放置好了皇后。
ll sum;
ll upperlim = ; // 试探算法从最右边的列开始。
void test(ll row, ll ld, ll rd)
{
if (row != upperlim)
{
// row,ld,rd进行“或”运算,求得所有可以放置皇后的列,对应位为0,
// 然后再取反后“与”上全1的数,来求得当前所有可以放置皇后的位置,对应列改为1
// 也就是求取当前哪些列可以放置皇后
ll pos = upperlim & ~(row | ld | rd);
while (pos) // 0 -- 皇后没有地方可放,回溯
{
// 拷贝pos最右边为1的bit,其余bit置0
// 也就是取得可以放皇后的最右边的列
ll p = pos&-pos; // 将pos最右边为1的bit清零
// 也就是为获取下一次的最右可用列使用做准备,
// 程序将来会回溯到这个位置继续试探
pos -= p;
// row + p,将当前列置1,表示记录这次皇后放置的列。
// (ld + p) << 1,标记当前皇后左边相邻的列不允许下一个皇后放置。
// (ld + p) >> 1,标记当前皇后右边相邻的列不允许下一个皇后放置。
// 此处的移位操作实际上是记录对角线上的限制,只是因为问题都化归
// 到一行网格上来解决,所以表示为列的限制就可以了。显然,随着移位
// 在每次选择列之前进行,原来N×N网格中某个已放置的皇后针对其对角线
// 上产生的限制都被记录下来了
test(row + p, (ld + p) << , (rd + p) >> );
}
}
else
{
// row的所有位都为1,即找到了一个成功的布局,回溯
sum++;
}
} int main()
{
int n;
while(scanf("%d",&n)!=EOF)
{
if(n==)
{
break;
}
sum = ;
upperlim = ( << n) - ;
test(,,);
printf("%lld\n",sum);
}
return ;
}
算法设计与分析——n后问题(回溯法+位运算)的更多相关文章
- 算法设计与分析 - AC 题目 - 第 5 弹(重复第 2 弹)
PTA-算法设计与分析-AC原题 - 最大子列和问题 (20分) 给定K个整数组成的序列{ N1, N2, ..., NK },“连续子列”被定义为{ Ni, Ni+, ..., Nj },其中 ≤i ...
- 算法设计与分析 - AC 题目 - 第 2 弹
PTA-算法设计与分析-AC原题7-1 最大子列和问题 (20分)给定K个整数组成的序列{ N1, N2, ..., NK },“连续子列”被定义为{ Ni, Ni+1, ..., Nj },其中 1 ...
- 【技术文档】《算法设计与分析导论》R.C.T.Lee等·第7章 动态规划
由于种种原因(看这一章间隔的时间太长,弄不清动态规划.分治.递归是什么关系),导致这章内容看了三遍才基本看懂动态规划是什么.动态规划适合解决可分阶段的组合优化问题,但它又不同于贪心算法,动态规划所解决 ...
- 算法设计与分析-Week12
题目描述 You are given coins of different denominations and a total amount of money amount. Write a func ...
- 『嗨威说』算法设计与分析 - 回溯法思想小结(USACO-cha1-sec1.5 Checker Challenge 八皇后升级版)
本文索引目录: 一.回溯算法的基本思想以及个人理解 二.“子集和”问题的解空间结构和约束函数 三.一道经典回溯法题点拨升华回溯法思想 四.结对编程情况 一.回溯算法的基本思想以及个人理解: 1.1 基 ...
- 算法设计与分析 - 李春葆 - 第二版 - html v2
1 .1 第 1 章─概论 1.1.1 练习题 1 . 下列关于算法的说法中正确的有( ). Ⅰ Ⅱ Ⅲ Ⅳ .求解某一类问题的算法是唯一的 .算法必须在有限步操作之后停止 .算法 ...
- 南大算法设计与分析课程复习笔记(1) L1 - Model of computation
一.计算模型 1.1 定义: 我们在思考和处理算法的时候是机器无关.实现语言无关的.所有的算法运行在一种“抽象的机器”之上,这就是计算模型. 1.2 种类 图灵机是最有名的计算模型,本课使用更简单更合 ...
- 算法设计与分析基础 (Anany Levitin 著)
第1章 绪论 1.1 什么是算法 1.2 算法问题求解基础 1.2.1 理解问题 1.2.2 了解计算设备的性能 1.2.3 在精确解法和近似解法之间做出选择 1.2.4 算法的设计技术 1.2.5 ...
- 算法设计与分析(李春保)练习题答案v1
1.1第1 章─概论 1.1.1练习题 1.下列关于算法的说法中正确的有(). Ⅰ.求解某一类问题的算法是唯一的 Ⅱ.算法必须在有限步操作之后停止 Ⅲ.算法的每一步操作必须是明确的,不能有歧义或含义模 ...
随机推荐
- 第五章 Unity中的基础光照(1)
[TOC] 渲染总是围绕着一个基础问题:我们如何决定一个像素的颜色?从宏观上来说,渲染包括了两大部分:决定一个像素的可见性,决定这个像素上的光照计算.而光照模型用于决定在一个像素上进行怎样的光照计算. ...
- java基础文件,File类
此文参考自"Java SE程序设计" 编著: 青岛东合信息技术有限公司 算是做笔记,以后想看可以翻阅,顺便分享出来大家可以参照.如有侵权,请联系本人删除 文件 文件是相关记录或放在 ...
- VIM操作记录
=============================================== 2019/12/12_第1次修改 vr7jj ============================= ...
- iOS开发经常用到的国外网站,让我们接轨国外的最新技术吧!
转自:http://www.bubuko.com/infodetail-787949.html 工欲善其事必先利其器,最近发现临时查找一些东西容易浪费时间,花了点时间整理一下常用的网站,方便以后备用. ...
- git 使用详解(4)—— commit -a -m/diff --staged/rm/mv
查看已暂存和未暂存的更新 实际上 git status的显示比较简单,仅仅是 列出了(修改过的.新创建的.已经暂存但未提交的)文件,如果要查看具体修改了什么地方,可以用git diff 命令.稍后我们 ...
- (全国多校重现赛一)F-Senior Pan
Senior Pan fails in his discrete math exam again. So he asks Master ZKC to give him graph theory pro ...
- 深入理解Linux的I/O复用之epoll机制
0.概述 通过本篇文章将了解到以下内容: I/O复用的定义和产生背景 Linux系统的I/O复用工具演进 epoll设计的基本构成 epoll高性能的底层实现 epoll的ET模式和LT模式 epol ...
- 【Visual Studio Code】插件
[Visual Studio Code]插件 转载:https://www.cnblogs.com/yangchongxing/p/10625628.html 目录 ================= ...
- DefinePlugin插件用法
作者:水涛 座右铭:天行健,君子以自强不息 自白:我写博文上来蹭蹭就是干,我突然觉得我需要幽默一点了,好了,下面我们说正经的 一.官方定义: DefinePlugin DefinePlugin 允许创 ...
- Spring Cloud Config入门(本地配置)
spring cloud config 简介 Spring Cloud Config为分布式系统中的外部化配置提供服务器和客户端支持.使用Config Server,您可以在所有环境中管理应用程序的外 ...