【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;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值