思路其实还是每使用一位就把横着,和左斜方,右斜方的位站上,但是用位运算就有不一样神韵。
先看代码:
int num;
int upper;
public int solve(int n){
upper=(1<<n)-1;
dp(0,0,0);
return num;
}
public void dp(int row, int ld,int rd){
System.out.println("dp: "+Integer.toBinaryString(row)+"\t"+Integer.toBinaryString(ld)+"\t"+Integer.toBinaryString(rd));
int pos,p;
if(row!=upper){
pos=upper&(~(row|ld|rd));
// System.out.println("pos= "+Integer.toBinaryString(pos));
while(pos!=0){
p=pos&(-pos);
pos=pos-p;
dp(row+p,(ld+p)<<1,(rd+p)>>1);
}
}else
num++;
}
主要精妙在这几个地方:容易看出来这是个dp,思路和普通解基本相同。
精妙1. 用row ld rd表示了横竖斜三个方向上的数据,用1表示占用,0表示可以。
精妙2. 用pos=upper&(~(row|ld|rd))来表示当前可行位置。 这个等会细说。
精妙3. 用p=pos&(-pos)表示第一个非0位。这个涉及到了负数在计算机里的表示。
byte在计算机里是8位,integer是32位,但是Integer.max_value 是(2<<31)-1 为什么?
因为剩下的第32位上是1的数都用来表示负数了,对于任何正整数v,他的负数表示为
~(v-1)
所以6&(-6)=2;
pos里面是用1表示可用,0表示不可用。
精妙4, 就是那个调用dp了,这里连上精妙2一起说一下。
其实我们很容易想到用横竖斜三种方式来表示三个纬度,第四个纬度自动步进1。问题就在于表述形式。在这里row ld rd都是用位来表示,在当前循环中,ld,rd,row都表示该列上被占用的位。 但这是怎么实现的呢?
1. row很容易理解,p为几,第几行就被占了,这没的说。就是ld,rd
2. 注意到列是步进的,也就是说,你在这一列,p占了第3行,下一列,一定被占在第4行上,和在第2行上。到了下下列,一定占在第1行和第5行上。这就是ld,和rd了。
(ld+p)<<1 不就是把所有的位左移1位挡住第4位么?
(rd+p)>>1 挡住了不就是第二位么?
第二次计算完了,到了下下列,再移动,挡住的不就是1行和5行了么?
这就是神