P1074 靶形數獨 題解
阿新 • • 發佈:2022-04-06
不愧是 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; }