为防止广告,目前nocow只有登录用户能够创建新页面。如要创建页面请先登录/注册(新用户需要等待1个小时才能正常使用该功能)。

USACO/checker

来自NOCOW
跳转到: 导航, 搜索

目录

[编辑] 分析

[编辑] DFS+优化

因为要遍历整棵搜索树,所以用DFS,我们可以在搜索时加入剪枝,记录横行和两条斜线是否被攻击,每次只放在不受任意方向攻击的地方。 这个剪枝过最后一个数据需要2s,超时。(准确的说,是1.2s -zst_0110)

[编辑] 考虑对称

如果n是偶数,第一行枚举前一半的数,这样每搜索出一种方案后,沿中线对称过来又是一种方案,并且因为第一行的数小于一半,对称过来的方案总大于原方案(即在搜索树中后被搜索到),不会出现重复的情况。

如果n是奇数,先在中间行和中间列放子,并且位置都不超过半数(<n div 2),且中间行>中间列,这样每搜索出一种方案,把它对称旋转后一共有8种方案,因为中间行和中间列的的不出现重复,所以8种方案不重复。这样只需枚举原来的1/8就可以了。 这样就完全可以过了。{注意放在正中间位置的时候}(个人感觉如果要求更严格,需要数学证明。我个人感觉上面上面的条件应该改为<=n/2,才对,而当中间行=中间列=(n+1)/2的时候,需要单独处理,如何单独处理呢,可以选择两种方法,一种是已经固定中间行和中间列然后进行枚举。一种是按照第一行的选择的列数小于(n+1)/2 枚举出来这个数然后乘以2。)

[编辑] 使用链表

还可以再优化,用链表(或用数组模拟)记录每行待选的位置,进行插入和删除,这样就不用记录横行是否被攻击了,并且每次枚举位置的个数减少。

[编辑] 简化问题

我们知道,每一行都只能填上一个皇后,并保证每行的皇后不相同 就以行为阶段,来考虑列的放置 那么,如何解决对角线的问题呢?

可证明的是,若在a行b列放上棋子,那么对于另一枚棋子x行y列

只需保证

x<>a,y<>b;
x+y<>a+b
x-y<>a-b

这样,就可以用三个极小的数组来储存禁止放置的情况(每次只需改变三个值);

[编辑] 最后一位算出来

由于n行每一行都要有一个,那么所有的皇后的位置之和必然为n×(n+1)/2,在dfs的时候记录已经枚举的皇后位置之和,那么最后一个可以直接算出来。这样的话最后一个点0.907s。

经过测试,这种算法是不可行的。要看RP~

[编辑] 注意!

如果是C++,DFS使用数组储存的话--- 小心缓冲区漏洞,不然会输出各种匪夷所思的答案。


[编辑] 直接回溯

所有大神貌似各种各样神奇算法,但是这道题我貌似什么优化都没有…… 直接回溯就可以了:用三个vis数组分别记录‘|’、‘/’、‘\’三种列、对角线是否已经有皇后了 然后就是回溯,毫无优化,但第一次就过了

void search(int cur) {

   if(cur==n)
   {
       num++;
       if(num<=3)
       {
           printf("%d",c[0]);
           for(int i=1;i<n;i++)
               printf(" %d",c[i]);
           printf("\n");
       }
       return;
   }
   for(int i=0;i<n;i++)
       if(!vis0[i]&&!vis1[i-cur+n-1]&&!vis2[cur+i])
       {
           c[cur]=i+1;
           vis0[i]=vis1[i-cur+n-1]=vis2[cur+i]=1;
           search(cur+1);
           vis0[i]=vis1[i-cur+n-1]=vis2[cur+i]=0;
       }

}

[编辑] DFS+位运算

可以考虑将问题分为两个子问题来处理:

a)求n皇后具体的方案;

b)求n皇后方案的个数;

对于问题a,因为只取前三个,用最简单的DFS即可办到.

对于问题b,可以考虑采用位运算,来递归模拟试放(考虑的条件与DFS类似),但不用储存具体方案.

用此方法通过全部数据耗时约0.4secs.源码
(根据位运算的具体操作细节时间还可以更快。我的是0.2左右--Ronice 23:23 2007年9月4日 (CST))

详细的位运算细节见matrix67的Blog:http://www.matrix67.com/blog/archives/266


[编辑] 全部细节位运算(DFS)

就是参照matrix67的Blog里求解的个数的算法,在输出前三个解这个要求上面加一点位运算技巧。连对称剪枝都不用最后一个点0.189秒杀!(还有倒数第二个0.027secs,其余全部0secs!) 详细如下: 对于matrix67的Blog所给的那个函数里(我写的C++版)

void test(int row,int ld,int rd)//row表示横行放皇后的情况,ld、rd分别是左右对角线的情况
{
     int pos,p;
     if(row!=upperlim)//upperlim表示全部都放上皇后的那种情况
     {
          pos=upperlim & ~(row|ld|rd);//pos表示还能在哪些位置上放皇后
          while(pos)
          {
                 p=pos & -pos;//p表示最右边的能放的那个位置
                 pos-=p;//放上这个位置
                 //***
                 test(row+p,(ld+p)<<1,(rd+p)>>1);//找下一个
          }
     }
     else
          sum++;
}

于是,如果要输出前三个解的话,需要记录,在每次递归调用test函数之前记录即可。 而怎样从那仅有的五个(row,ld,rd,pos,p)中找出你所需要的那个位置是几呢? 首先看,sum是当前已经有几个解了,所以在sum<3的情况下记录答案。 其次看,USACO对那个输出的要求,仅需要输出列号,而在此函数中,那个p的二进制的唯一的那个一就表示这个列号,用log2(p)+1轻松取得。(需要<math.h>或者<cmath>) 最后,那是第几行的呢?这个东西好像也可以在test函数中多加一个值传递进去,不过,用二进制怎么实现呢? 再次从matrix67大牛的Blog上http://www.matrix67.com/blog/archives/264 找到求一个二进制数中1的个数的方法,废话不多说,我写的C++版:

inline int number(int row)
{
     row=(row&a[0])+((row>>1)&a[0]);
     row=(row&a[1])+((row>>2)&a[1]);
     row=(row&a[2])+((row>>4)&a[2]);
     row=(row&a[3])+((row>>8)&a[3]);
     row=(row&a[4])+((row>>16)&a[4]);
     return row;
}

其中const int a[]={0x55555555,0x33333333,0x0F0F0F0F,0x00FF00FF,0x0000FFFF}; 此时,我们就有了整个***处所要加的语句,如下

if(sum<3)
{
          int num=number(row),pnt=log2(p)+1;
          for(int i=sum;i<3;i++)
          list[i][num]=pnt;//list便是记录答案的数组,最节约的大小为list[3][13]
}

至此结束,位运算很神奇,是不?

[编辑] 参考代码

C

C++

Pascal

Java

[编辑] 引用

[1]

[2]

个人工具