算法设计与分析——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.下列关于算法的说法中正确的有(). Ⅰ.求解某一类问题的算法是唯一的 Ⅱ.算法必须在有限步操作之后停止 Ⅲ.算法的每一步操作必须是明确的,不能有歧义或含义模 ...
随机推荐
- 【JMX】jmx结合jmx_exporter实现promethues监控
JMX JMX的全称为Java Management Extensions. 顾名思义,是管理Java的一种扩展.这种机制可以方便的管理.监控正在运行中的Java程序.常用于管理线程,内存,日志Lev ...
- pikachu 暴力破解
一 暴力破解 1.基于表单的暴力破解 先随意测试root/root登录,用Burp抓包,丢进Intruder 添加username和password两个参数变量,攻击类型选择Clusterbomb 有 ...
- SpringBoot 发送邮件功能实现
背景 有个小伙伴问我你以前发邮件功能怎么弄的.然后我就给他找了个demo,正好在此也写一下,分享给大家. 理清痛点 发送邮件,大家可以想一下,坑的地方在哪? 我觉得是三个吧. 第一:邮件白名单问题. ...
- JQuery之选择集过滤
JQuery选择集过滤应用如下: 代码实现: <script src="JS/jquery-3.4.1.js"></script> <script&g ...
- Chrome快捷键吐血整理
Chrom是平时开发过程中最常用到的浏览器,使用快捷键操作Chrome能提高我们的使用效率,而且可以脱离鼠标进行操作.本篇博客就对平时我们常用的Chrome快捷键做一个整理总结.大家拿走不谢,哈哈~~ ...
- Cannot forward after response has been committed问题的解决
Cannot forward after response has been committed问题解决及分析 通过TOMCAT把系统启动,可以正常登陆门户,登陆进去选择子系统的时候点击登陆的时候,可 ...
- 第六章 jQuery选择器
jQuery选择器概述: 选择器jQuery基础,在jQuery中,对事件处理,遍历DOM和Ajax操作都依赖于选择器. 什么是jQuery选择器: jQuery选择器拥有良好的浏览器兼容性,不用使用 ...
- Demo00
Demo00 std::transform在指定的范围内应用于给定的操作,并将结果存储在指定的另一个范围内.要使用std::transform函数需要包含头文件. 以下是std::transform的 ...
- 函数中this的指向
每个函数在被调用时都会自动取得两个特殊变量:this和arguments:内部函数在搜索这两个变量时,只会搜索到其活动对象为止,因此永远不可能直接访问外部函数中的这两个变量. f 1 var name ...
- Web基础了解版03-jQuery
jQuery jQuery,顾名思义,也就是JavaScript和查询(Query)极大地简化了JavaScript开发人员遍历HTML文档.操作DOM.处理事件.执行动画和开发Ajax. jQuer ...