【NOIP2009提高组T4】靶形数独-DFS剪枝+位运算优化
测试地址:靶形数独
做法:最朴素的DFS耗时较大,所以我们来想想应该如何优化。
如果每次都用9次运算来求一行,一列和一个九宫格中填了哪些数,时间开销显然很大。于是,我们可以用位运算来解决这个问题,这样就可以把状态压缩成用1次运算就可以求出这些东西。用line[i],column[i]表示第i行和第i列的状态(注意,为了计算方便,我们的行和列都以0~8编号),这里的状态是指已填了哪些数。如果第i行里填了数字j,那么line[i]转换成二进制数后从右往左第j位就为1。然后,用block[i][j]表示九宫格(包含(i*3,j*3),(i*3,j*3+1),(i*3,j*3+2),(i*3+1,j*3),(i*3+1,j*3+1),(i*3+1,j*3+2),(i*3+2,j*3),(i*3+2,j*3+1),(i*3+2,j*3+2),其中0≤i,j≤2)的状态。如果你对于状态压缩DP这类运用位运算优化的算法较为熟悉的话,应该很快能理解这些东西。
再想想,我们平时解数独时,都是怎么解的?大多数人应该都是从已知信息最多的行(列,九宫格)开始填。这就是一个优化的思路:优先从空位最少(但不为0)的行开始填写,以至于刚开始状态不至于发散得太多,而把空位最多的行留到最后填。这是为什么呢?因为越到后面,已填满的行数越多,限制条件就越多,剪枝就越强。事实证明,这个优化思路是很有效的。
弄清这些东西之后,就很朴素的DFS就可以了,数据比较弱,最大的点约960ms可过。
以下是本人代码:
#include <cstdio>
#include <cstdlib>
#include <cmath>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
int a[10][10],block[3][3]={0},line[10]={0},column[10]={0},linefill[10]={0},f[10]={0},ans=-1;
int st[10]={0,1,2,3,4,5,6,7,8,0};
int score[9][9]=
{
{6,6,6,6,6,6,6,6,6},
{6,7,7,7,7,7,7,7,6},
{6,7,8,8,8,8,8,7,6},
{6,7,8,9,9,9,8,7,6},
{6,7,8,9,10,9,8,7,6},
{6,7,8,9,9,9,8,7,6},
{6,7,8,8,8,8,8,7,6},
{6,7,7,7,7,7,7,7,6},
{6,6,6,6,6,6,6,6,6}
};
bool cmp(int a,int b)
{
return f[a]<f[b];
}
int calc() //计算分数
{
int s=0;
for(int i=0;i<9;i++)
for(int j=0;j<9;j++)
s+=a[i][j]*score[i][j];
return s;
}
void dfs(int k) //填第st[k]行
{
if (k==10)
{
int newans=calc();
if (newans>ans) ans=newans;
return;
}
int i=st[k],j;
int x=511-linefill[i],y; //511(10)=111,111,111(2)
y=x&-x;
j=(int)log2(y);
int p=511-(line[i]|column[j]|block[i/3][j/3]);
linefill[i]|=y;
while(p>0)
{
int ps=p&-p;
p-=ps;
a[i][j]=(int)log2(ps)+1;
line[i]|=ps;
column[j]|=ps;
block[i/3][j/3]|=ps;
if (x==y) dfs(k+1); //x=y表示当前行只剩当前所填的空位,则填写下一行
else dfs(k);
line[i]-=ps;
column[j]-=ps;
block[i/3][j/3]-=ps; //回溯
}
linefill[i]-=y;
}
int main()
{
for(int i=0;i<9;i++)
{
for(int j=0;j<9;j++)
{
scanf("%d",&a[i][j]);
if (a[i][j]>0)
{
linefill[i]|=1<<j;
int p=1<<(a[i][j]-1);
if ((line[i]&p)!=0||(column[j]&p)!=0||(block[i/3][j/3]&p)!=0)
{
printf("-1\n");
return 0;
}
line[i]|=p;
column[j]|=p;
block[i/3][j/3]|=p;
f[i]++;
}
}
f[i]=9-f[i]; //f[i]为第i行的空位数,用于排序
}
sort(st+1,st+10,cmp); //对st数组排序,排序后st数组就为填写的顺序
int start=1;
while(f[st[start]]==0) start++; //找空位最少且不为0的行
dfs(start);
printf("%d",ans);
return 0;
}