P1074 靶形数独 题解
不愧是 2009 Noip tg T4 ,连续打了4天的代码,吸了口氧才通过。
前置知识:
对于一道数独题,我们可以先预处理出每一行0的个数,然后从个数最少的行开始做,这样可以节省大量的时间(因为这些格子可以填的数字少)。
对于本题,我一开始的思路是:仿照前置知识预处理,分数进行打表,存在 \(Point\) 数组里面,用下列的三个函数进行填数之后的标记处理(这里我令 \(a_{i,j,k}\) 为第 \((i,j)\) 的格子能否填数字 \(k\) ), dfs 填表时按照行去处理,部分代码如下:
//分数打表自动略去
//b数组是我用来存放数独的数组
//以下是预处理代码
struct node
{
int num,x;
}sum[MAXN];
void GetShun()
{
for(int i=1;i<=9;i++) sum[i].x=i;
for(int i=1;i<=9;i++)
for(int j=1;j<=9;j++)
{
if(b[i][j]==0) sum[i].num++;
}
sort(sum+1,sum+9+1,cmp);
}
//获知应该搜索的行,将其存在sum结构体中
void GetC()
{
for(int tmp=1;tmp<=9;tmp++)
{
for(int i=nx[tmp][0];i<=ny[tmp][0];i++)
for(int j=nx[tmp][1];j<=ny[tmp][1];j++)
{
c[i][j]=tmp;
}
}
}
//获取每一个点所在的九宫格,nx,ny数组表示每一个宫格左上角与右下角的坐标,0为左上角,1为右下角
void Deal(int x,int y)
{
for(int i=1;i<=9;i++) a[x][i][b[x][y]]++;
for(int i=1;i<=9;i++) a[i][y][b[x][y]]++;
for(int i=nx[c[x][y]][0];i<=ny[c[x][y]][0];i++)
for(int j=nx[c[x][y]][1];j<=ny[c[x][y]][1];j++)
a[i][j][b[x][y]]++;
}
//做标记
void reDeal(int x,int y)
{
for(int i=1;i<=9;i++) a[x][i][b[x][y]]--;
for(int i=1;i<=9;i++) a[i][y][b[x][y]]--;
for(int i=nx[c[x][y]][0];i<=ny[c[x][y]][0];i++)
for(int j=nx[c[x][y]][1];j<=ny[c[x][y]][1];j++)
a[i][j][b[x][y]]--;
}
//恢复现场
//dfs按照行去处理,具体思路是按照sum数组存的行搜索,对每一行的所有0进行填表,每填一个就继续递归dfs,用last_zero表示这一行的0有没有处理完,不过代码好像丢了。。。
//对于每个关键数组和变量的意义在全部代码中会详细给出,想知道的读者可以先看全部代码再返回
结果样例测完后就发现,这种方法极其不稳定,很容易在 dfs 时串行(这行还没搜索完就到下一行),所以我们要想一个更稳定的方法。
实际上,如果吸口氧前面的预处理已经足够优秀了,关键是如何决定搜索顺序。
行如果不稳定,那么按列搜???好像差不多
等等,好像除了行和列,还有点吧!!!
所以我决定按点搜。
我在代码中新开了一个数组(结构体) \(Dfsr\) ,用来存放搜点的顺序,其中 \(tmp\) 是零点的数量(对上面代码中的 \(GetC\) 无影响),处理时还是按照 \(sum\) 数组进行处理,从最少的行开始依次存0点,然后进行搜索。这样,吸口氧就能过去了。
注意本题细节问题很多,并且很容易因为按行搜而困住(如果您按行搜过了那您就太厉害了, Orz )
全部代码如下:
#include<bits/stdc++.h>
using namespace std;
const int MAXN=9+5;
int a[MAXN][MAXN][MAXN],b[MAXN][MAXN],c[MAXN][MAXN];
//b:存放数独的数组 a:a[i][j][k]表示(i,j)能否填数字k,c:c[i][j]表明(i,j)所在的宫格
int nx[MAXN][2]={{0,0},{1,1},{1,4},{1,7},{4,1},{4,4},{4,7},{7,1},{7,4},{7,7}};
int ny[MAXN][2]={{0,0},{3,3},{3,6},{3,9},{6,3},{6,6},{6,9},{9,3},{9,6},{9,9}};
//nx,ny:nx,ny数组表示每一个宫格左上角与右下角的坐标,0为左上角,1为右下角
int Point[MAXN][MAXN]={
{0,0,0,0,0,0,0,0,0,0,0},
{0,6,6,6,6,6,6,6,6,6,0},
{0,6,7,7,7,7,7,7,7,6,0},
{0,6,7,8,8,8,8,8,7,6,0},
{0,6,7,8,9,9,9,8,7,6,0},
{0,6,7,8,9,10,9,8,7,6,0},
{0,6,7,8,9,9,9,8,7,6,0},
{0,6,7,8,8,8,8,8,7,6,0},
{0,6,7,7,7,7,7,7,7,6,0},
{0,6,6,6,6,6,6,6,6,6,0}};
//Point:分数的打表
int ans=-1,tmp;
//ans为答案,初始值为-1可以方便后续答案的统计,tmp是0点的个数
struct node
{
int num,x;
}sum[MAXN];
//sum:处理行的顺序,x=第x行,num=该行0点个数
struct node2
{
int r,c;
}Dfsr[MAXN*100];
//Dfsr:处理0点的搜索顺序
bool cmp(const node& fir,const node& sec)
{
return fir.num<sec.num;
}
//对sum数组按照0点个数升序排序
void GetShun()
{
for(int i=1;i<=9;i++) sum[i].x=i;
for(int i=1;i<=9;i++)
for(int j=1;j<=9;j++)
{
if(b[i][j]==0) sum[i].num++;
}
sort(sum+1,sum+9+1,cmp);
}
//处理sum数组
void GetZero()
{
for(int i=1;i<=9;i++)
{
int x=sum[i].x;
for(int j=1;j<=9;j++)
{
if(b[x][j]==0)
{
Dfsr[++tmp].r=x;Dfsr[tmp].c=j;
}
}
}
return ;
}
//处理Dfsr数组
void GetC()
{
for(int tmp=1;tmp<=9;tmp++)
{
for(int i=nx[tmp][0];i<=ny[tmp][0];i++)
for(int j=nx[tmp][1];j<=ny[tmp][1];j++)
{
c[i][j]=tmp;
}
}
}
//处理c数组
void Deal(int x,int y)
{
for(int i=1;i<=9;i++) a[x][i][b[x][y]]++;
for(int i=1;i<=9;i++) a[i][y][b[x][y]]++;
for(int i=nx[c[x][y]][0];i<=ny[c[x][y]][0];i++)
for(int j=nx[c[x][y]][1];j<=ny[c[x][y]][1];j++)
a[i][j][b[x][y]]++;
}
//做标记
void reDeal(int x,int y)
{
for(int i=1;i<=9;i++) a[x][i][b[x][y]]--;
for(int i=1;i<=9;i++) a[i][y][b[x][y]]--;
for(int i=nx[c[x][y]][0];i<=ny[c[x][y]][0];i++)
for(int j=nx[c[x][y]][1];j<=ny[c[x][y]][1];j++)
a[i][j][b[x][y]]--;
}
//恢复现场
int Max(int fir,int sec)
{
return (fir>sec)?fir:sec;
}
void GetAnswer()
{
int getans=0;
for(int i=1;i<=9;i++)
for(int j=1;j<=9;j++)
{
getans+=b[i][j]*Point[i][j];
}
ans=Max(ans,getans);
}
//获取ans
void dfs(int k)
{
if(k==tmp+1)
{
GetAnswer();
return ;
}
for(int i=1;i<=9;i++)
{
if(!a[Dfsr[k].r][Dfsr[k].c][i])
{
b[Dfsr[k].r][Dfsr[k].c]=i;
Deal(Dfsr[k].r,Dfsr[k].c);
dfs(k+1);
reDeal(Dfsr[k].r,Dfsr[k].c);
}
}
}
//按点搜索,k为第k个0点
int main()
{
for(int i=1;i<=9;i++)
for(int j=1;j<=9;j++)
scanf("%d",&b[i][j]);
GetShun();
GetC();
GetZero();
for(int i=1;i<=9;i++)
for(int j=1;j<=9;j++)
{
if(b[i][j]==0) continue;
Deal(i,j);
}
dfs(1);
printf("%d\n",ans);
return 0;
}