noip2009靶形数独题解 转自middlesch_nce24的微博http://blog.sina.com.cn/s/blog_5d0d0f450100jm6u.html#

来源:互联网 发布:js数组按数字大小排序 编辑:程序博客网 时间:2024/06/09 20:17

转自http://blog.sina.com.cn/s/blog_5d0d0f450100jm6u.html#

感谢middlesch_nce24

看完这道题,我觉得我深搜白学了

Noip2009靶形数独解题报告(位运算版)

         

实现方法:搜索                                                                     

技    巧:位运算                                                                 

 

此题以及类似题目(如八皇后),最明智的选择都是位运算实现。

理由:

思考:普通搜索时间都浪费到哪里了?

答曰:1.查哪一位是空的,也就是没填;

2.试探这一个空位可否填这个数或者找出这位可填的数。于是你要把这一行的数过一遍,再把这一列的数过一遍,再把所在的小九宫格过一遍。

每次都这么做,时间就浪费了。

 

思考:能否用一步运算做到1.找空位;2.找出可填的数。

     答曰:可以。位运算技巧多多,是这类搜索题的最佳选择。

 

如果你对位运算不是很了解,可以先看matrix67大牛对位运算的讲解(4节内容),以下是他的博客地址:

http://www.matrix67.com/blog/archives/263

你若是理解了其八皇后的做法,那么你的位运算已经差不多了,足以用位运算解此题。

 

以下是我的源程序+简略的解释:

(看的顺序当然是先看主程。)由于新浪博客字数限制,故删除了一些东西(如评测结果),甚至不得不删掉注释里的空格,导致注释和代码挤的很难看。

#include<stdio.h>

#include<math.h>

int h[10]={},hs[10]={},zs[10]={},xj[5][5]={},hq[10]={};//全都清零一下,具体意思下面解释。

int ans=-1,st[10],a[10][10];

void make()   //这个是算分值,更新ans

{

    int sum=0,i,j;

    for (i=1;i<5;i++)

      {for (j=i;j<11-i;j++)

        sum+=(a[i][j]+a[10-i][j])*(5+i);

       for (j=i+1;j<10-i;j++)

        sum+=(a[j][i]+a[j][10-i])*(5+i);

        }

    sum+=a[5][5]*10;   

    if (sum>ans) ans=sum;

}

void dfs(int k)  //搜索部分,k不表示搜索第k行,而是第st[k]

{

     if (k==10) make(); // k=10表示九行都搞定了,开始算分

     else

     {

      int x,y,j,pos,p,i=st[k]; //i表示第i行,j表示第j

      x=511-h[i];//51110=1111111112),故此行的意思是将第i行缺位取出来

                此时x1表示缺位,yx

      y=x&-x; //y是取出本行第一个缺位,在这一次搜索里就搜索这个缺位

      h[i]|=y;//下一次搜索时,这一位已填,故把缺位补上

      j=(int)log2(y)+1; //j就是y用二进制表示1所在的位数,即j

      pos=511-(hs[i]|zs[j]|xj[(i-1)/3][(j-1)/3]); //这一步是取出可以填哪些数

      while (pos>0)

      {p=pos&-pos;  //取出可以填的一个数

       pos-=p;       //去掉已填的数

       a[i][j]=(int)log2(p)+1; //填入a

       hs[i]|=p;    //修改hszsxj,这个数已用过,‘或’写成‘+’也行

       zs[j]|=p;

       xj[(i-1)/3][(j-1)/3]|=p;

       if (x==y) dfs(k+1);//x=y,则这一行只有一个空,即现在已填的空,故搜索k+1

       else dfs(k); //x<>y,则这一行还有空没填,继续搜索这一行

       hs[i]-=p; //搜索完,还原hszsxj

       zs[j]-=p;

       xj[(i-1)/3][(j-1)/3]-=p;

       };

       h[i]-=y;  //s搜索完,还原h[i]

       };

}

int main()

{

    int i,j,p0;

    for (i=1;i<10;i++)

      for (j=1;j<10;j++)

        {scanf("%d",&a[i][j]); //读入数独,数组a记的是数独。

         if (a[i][j]>0)

           {h[i]|=1<<(j-1);  //数组h记的是某一行填数情况

                       //h[i]写成二进制,第j位为0,表示a[i][j]=0,即没填同理第j位为1,表示a[i][j]>0,已填数

            p0=1<<(a[i][j]-1); //p0写成二进制,第k位为1,表示数字k已用过

            if (((hs[i]&p0)!=0)||((zs[j]&p0)!=0)||

                       ((xj[(i-1)/3][(j-1)/3]&p0)!=0))

            {printf("-1\n");return 0;};  //这个判断是看数独有没有错,即某一行(列,九宫格)是否有同一数字出现两次

            hs[i]|=p0;  //数组hs记的是某一行数字用的情况

                       //hs[i]写成二进制,第j位为0,表示i行,j没用过

                           同理第j位为1,表示i行,j用过

            zs[j]|=p0;    //数组zs记的是某一列(纵行)数字用的情况,意义同hs

 

            xj[(i-1)/3][(j-1)/3]|=p0; //数组xj记的是某一小九宫格数字用的情况,意义同hs

            }        //九个小九宫格分别是   xj[0][0] xj[0][1] xj[0][2]

        xj[1][0] xj[1][1] xj[1][2]

         xj[2][0] xj[2][1] xj[2][2]

          else hq[i]++;}  //数组hq记的是某一行缺数的个数,唯一的优化的组成部分。

    for (i=1;i<10;i++) st[i]=i;         //数组st记的是搜索各行的顺序,就是先搜哪一行,再搜哪一行

    for (i=1;i<9;i++)                 //此部分是按各行空缺数的个数将st从小到大排序

      for (j=i+1;j<10;j++)            //使得一会搜的时候,先搜缺数少的行,这也就是唯一的优化

       if (hq[st[i]]>hq[st[j]])

         {st[i]^=st[j];               //交换两数位运算版

          st[j]^=st[i];

          st[i]^=st[j];}

         

    for (i=1;hq[st[i]]==0;i++);         //考虑到某一行缺数可能为0,故先找到缺数的行

    dfs(i);                          //开始搜索

    printf("%d\n",ans);               //ans就是答案

    return 0;

}

 

怎么样,搜索的很朴素吧,但同样很快,能AC这道题。

0 0