[NOIP 2009]靶形数独

相当坑爹的一道题,也是NOIP2009年最后一题,相信难度已不必多说,而且也是唯一一道时限2s的题目。
网上有人用Dancing Links做此题,那就只能ORZ......
数独问题,是已经被证明了的NP完全问题,但是由于NOIP不可能因为Dancing Links去卡数据,所以较为优秀的方法应该是启发式搜索(亦或可以说是A*)
整体上的思路是,定义两个数组
int line[10],row[10] 代表每行每列剩余可填的数的个数
然后分别从小到大排序,确定一个搜索顺序——优先选择可填数少的地方(这个没争议吧,也不需要解释,平常用人脑玩数独时都是这个思路),即可dfs。
下面上代码吧(不易看清的地方都用了注释):

#include <iostream>
#include <cstdlib>
#include <cmath>
#include <algorithm>
using namespace std;
struct RestBlankWithId
{
    int x,id; //x is the sum of rest blank , id is this line(or row)'s origin number
    bool operator<(const RestBlankWithId comp_obj)const
    {
        return x>comp_obj.x;
    }
};
int a[10][10],m[10][10],ans=-1;
bool x[10][10],y[10][10],n[10][10]; 
// if point (x,y) in it's line , row or the sudoku it belongs to
RestBlankWithId row[10],line[10]; 
// make the sum of rest blank in order , keep it's id , make sure which best A* order it will be
int order[81][2],tot_order=1; //best A* order in record
void A_star()
{
    for(int i=1;i<=9;i++)
        row[i].id=line[i].id=i;
    sort(row+1,row+10);
    sort(line+1,line+10);
    for(int i=1;i<=9;i++)
        for(int j=1;j<=9;j++)
            if(!a[line[i].id][row[j].id])
            {
                order[tot_order][0]=line[i].id;
                order[tot_order++][1]=row[j].id;
            }
}
int belong(int i,int j) //jugde which sudoku this point belongs to
{
   int xt = 0,yt = 0;
   for(int k=6;k>=0;k-=3)
       if(i-k>0)
       {
           xt=(k+3)/3;
           break;
       }
   for(int k=6;k>=0;k-=3)
       if(j-k>0)
       {
           yt=(k+3)/3;
           break;
       }
   return yt+(xt-1)*3;
}
void init()
{
    for(int i=1;i<=9;i++)
        for(int j=1;j<=9;j++)
        {
            m[i][j]=10-max(abs(i-5),abs(j-5)); //each point mark
            x[i][j]=y[i][j]=n[i][j]=true;
        }
    for(int i=1;i<=9;i++)
        for(int j=1;j<=9;j++)
        {
            cin>>a[i][j];
            if(a[i][j])
            {
                line[i].x++;
                row[j].x++;
            }
            x[i][a[i][j]]=false;
            y[j][a[i][j]]=false;
            n[belong(i,j)][a[i][j]]=false;
        }
}
void score()
{
    int sum=0;
    for(int i=1;i<=9;i++)
        for(int j=1;j<=9;j++)
            sum+=m[i][j]*a[i][j];
    ans=max(sum,ans);
}
void dfs(int step)
{
    if(step==tot_order)
        score();
    else
       for(int k=1;k<=9;k++)
       {
           int px=order[step][0],py=order[step][1];
           if(n[belong(px,py)][k]&&x[px][k]&&y[py][k])
           {   //number k must not appeal in same line , row or it's sudoku
               a[px][py]=k; //try to push k in Map[px,py]
               n[belong(px,py)][k]=x[px][k]=y[py][k]=false; //pre deal before dfs
               dfs(step+1);
               n[belong(px,py)][k]=x[px][k]=y[py][k]=true; //recover
           }
       }
}
int main()
{
    init();
    A_star();
    dfs(1);
    cout<<ans;
    return 0;
}

最后再扯一句,至此,终于有一年的NOIP能AK了!

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值