P1074 靶形数独 题解

原题传送门

不愧是 2009 Noip tg T4 ,连续打了4天的代码,吸了口氧才通过。

前置知识:

对于一道数独题,我们可以先预处理出每一行0的个数,然后从个数最少的行开始做,这样可以节省大量的时间(因为这些格子可以填的数字少)。

对于本题,我一开始的思路是:仿照前置知识预处理,分数进行打表,存在 P o i n t Point Point 数组里面,用下列的三个函数进行填数之后的标记处理(这里我令 a i , j , k a_{i,j,k} ai,j,k 为第 ( i , j ) (i,j) (i,j) 的格子能否填数字 k k 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 时串行(这行还没搜索完就到下一行),所以我们要想一个更稳定的方法。

实际上,如果吸口氧前面的预处理已经足够优秀了,关键是如何决定搜索顺序。

行如果不稳定,那么按列搜???好像差不多

等等,好像除了行和列,还有点吧!!!

所以我决定按点搜。

我在代码中新开了一个数组(结构体) D f s r Dfsr Dfsr ,用来存放搜点的顺序,其中 t m p tmp tmp 是零点的数量(对上面代码中的 G e t C GetC GetC 无影响),处理时还是按照 s u m sum 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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值