题目:
在 n*n(n≤20)的方格棋盘上放置 n 个车(可以攻击所在行、列),求使它们不能互相攻击的方案总数。
思路:
根据组合数学很明显是n!(n的阶乘)
我们把二进制中的 1 看做放了一个车,0 作为不放;
整个模板我们以n = 5的5*5的矩阵为例子
①开个for:1 to (1< 为什么是(1< 一开始for循环是一行一行找,找到最后每一列都有车,显然这就是我们要找的最终状态; 介于我一开始对二进制的减法也一脸懵逼,这里也讲讲; 我先举个十进制的例子,81-9= 8 1 由于被减数的个位1<减数的个位9, 7, 11 - 9 所以我们向被减数的十位8借位,- 9 ———— 左边的竖式可以写成右边这种;————— 72 72 没错,所有进制的减法都是这样,关键就在一个“借位”上,我再举一个二进制的例子:11000011-101101= 是几进制就借几,比如十进制个位要向十位借,个位+10,十位-1;比如二进制10-1,就是低位0向高位1借2,0+2=2,1-1=0,形如02-1=1; ②然后考虑列,每一列只能有一个车,也就是说由当前情况往前找时,只要记录下哪一列有车,然后不去找他就可以了,这一点可以由位运算s & (1<<(i-1))实现; 相信很多刚开始接触的人跟我一样懵逼,这些位运算都是啥啊,根本看不懂啊,所以我们举个例子来解释一下: 先上一个流传很广的图: 这个图是这样的,我们设一个状态 s=01101,就表示第一、三、四列(从低位开始)已经放置了车; 由于我们是一行一行放的,所以在s状态我们应该放第三行了,那第三行我们应该放在哪呢,或者说状态s由哪些状态来的呢?就由上图来解答。 状态s(01101)由三种状态(必然是前两行的状态)来的: 前两行在三、四列放了车,第三行只好放在第一列;(01100) 前两行在一、四列放了车,第三行只好放在第三列;(01001) 前两行在一、三列放了车,第三行只好放在第四列;(00101) (蓝绿代表前两行的方案数,如果不懂这个我们后面解释) 无非就是这三种情况,现在我们来考虑怎么来表示状态s由这三种状态来的,还记得我们前面说的“s & (1<<(i-1))”吗,靠他实现; 如果对位运算不熟,先来打打位运算的基础: and 运算&相同位都为1,则为1;若有一个不为1,则为0 s=01101 由 01100、01001、00101三个状态来的,观察可以发现这个三个状态对应01101分别在一、三、四列少一个1; 所以我们可以设想,只有第i列有车的状态(10000、01000、00100、00010、00001)与s=01101进行某种操作,使我们可以得到这三种状态(即得到此列是否可以放车),这就是 s & (1<<(i-1)) 的作用(&优先级大于&,注意用()括起来); 带大家走一遍,for(i: 0 to 4) 01101 & (1<<0) = 01101 & 1 = 00001 01101 & (1<<1) = 01101 & 10 = 00000 01101 & (1<<2) = 01101 & 100 = 00100 01101 & (1<<3) = 01101 & 1000 = 01000 01101 & (1<<4) = 01101 & 10000 = 00000 我们可以清楚的看出来了,只有和s=01101有1重合结果才大于0,可以根据这个特性判断此列是否可以放车; 注意了,i对应列,(i-1)对应位置; ③f[s]表示在状态S下的方案数,状态s是二进制,注意边界条件f[0] = 1; ④如果满足 s & (1<<(i-1)) 条件,即第i列可以放车,我们又看到了新的位运算temp = s ^ (1<<(i-1)),我们来走一遍,由 s & (1<<(i-1)) 确定只有一、三、四列满足情况 01101 ^ (1<<0) = 01101 ^ 1 = 01100 01101 ^ (1<<2) = 01101 ^ 100 = 01001 01101 ^ (1<<3) = 01101 ^ 1000 = 00101 没错,由 s & (1<<(i-1)) 确定了我们一直想找的s=01101的三个状态; ⑤f[s] += f[temp]; 在②中我不是说蓝绿代表前两行的方案数,这什么意思呢,解释下 推广状态s,那么f[s] = Σf[s ^ (1<<(i-1))],其中i是枚举的状态s中每一个1的位置(从低位到高位)。 ( 上表来自大神) 我一开始看觉得很奇怪啊,为什么这么多2这么多6,是干嘛呀,其实是这样的: 以2: 2 2 2 2 2 2 2 2 2 2 2!为例:“有两个1的情况”中有C52(从五列中任选两列)=5*4/(2*1) = 10种状态(所以有10个2),而每种状态有2!(阶乘)种方案,如果再问为什么,看看上面那个图,蓝绿代表前两行的一种状态的方案数; 那 s=01101怎么看呢,他不过就是“有三个1的情况”的10个状态中的一种,只不过此s状态又由 01100、01001、00101三个子状态得来罢了(都用了“状态”两字,可能容易混淆,仔细看); 所以f[s] += f[temp]很好理解了吧,以s=01101为例,f[13] += f[12] + f[9] + f[5] = 2 + 2 + 2 = 6,即01101状态有六种方案数,看下图理解一下: 代码: 反思: 在把f[1<<21]开在主函数内时,出现“已停止工作”的情况,编译成功,但无法运行; 经过探索发现这种情况叫“栈溢出”,局部变量是在栈上分配空间的,栈默认大小一般为1-2M,int f[1<<21]的大小为2^21(2,097,152) ≈ 2.1^1,000,000,2M多一点,编译连接时不会有问题,但运行时由于栈溢出,程序异常终止;而全局变量在静态存储区分配内存,理想情况2-3G吧 而栈的大小与不同的编译器有关,栈默认大小一般为1-2M,一旦出现死循环或者是大量的递归调用,在不断压栈的过程中,容易造成栈容量超过1M而导致溢出。 参考博客:http://blog.csdn.net/luqiang454171826/article/details/6133972
or 运算| 相同位只要一个为1即为1
xor 运算^ 相同位不同则该位为1, 否则该位为0
not 运算~ 把0和1全部取反
shl 运算<< a< shr 运算>> a>>b就是把a转为二进制后右移b位(即去掉末尾b个位),相当于a除以2的b次方(取整)
边界条件:f[0] = 1。
输出f中所有状态的方案数,按照S中1的个数分类:
0:1 0!
1:1 1 1 1 1 1!
2:2 2 2 2 2 2 2 2 2 2 2!
3:6 6 6 6 6 6 6 6 6 6 3!
4:24 24 24 24 24 4!
5:120 5!
#include