轉自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];//511(10)=111,111,111(2),故此行的意思是將第i行缺位取出來
此時x中1表示缺位,y與x同
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; //修改hs,zs,xj,這個數已用過,‘或’寫成‘+’也行
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; //搜索完,還原hs,zs,xj
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這道題。