zoukankan      html  css  js  c++  java
  • 【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
    ");
    		  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;
    }
    


  • 相关阅读:
    WPF 如何画一颗心
    WPF 通过Border来画边框
    WPF 如何引入外部样式
    WPF 变量转换的实现
    WPF 动画显示控件
    wpf 悬浮窗口的实现
    WPF 如何绘制不规则按钮,并且有效点击范围也是不规则的
    WPF 变量绑定实现
    2016年终总结:从程序员到项目经理的转身
    使用FastReport打印二维码
  • 原文地址:https://www.cnblogs.com/Maxwei-wzj/p/9793909.html
Copyright © 2011-2022 走看看