1. 程式人生 > 其它 >P1074 靶形數獨 題解

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