noip2009靶形數獨題解 轉自middlesch_nce24的微博http://blog.sina.com.cn/s/blog_5d0d0f450100jm6u.html#

轉自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這道題。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章