一本通&&洛谷 ——靶型数独——题解
主要是搜索顺序不同导致效率千差万别。
联想人做数独的策略,总是先填可填数最少的那个空,再填选择第二少的。。。其实这种策略就造就了一个深度浅时分支也较少的搜索树。合适的搜索顺序再配合剪枝==AC。
所以搜索顺序为:从当前可填数的数目最少的那个空开始。注意:每填一个空,都会导致同行同列同九宫格的空的可填数数目减一,所以不能一开始就排序。(实测这样做会比正解慢8倍左右,真是恐怖如斯)所以要在分支选择时去找。为了平衡准确性与高效性,可以用一个简单的判断找到一个比较合适的空去填(因为如果每次都精确去找那个可填数数目最小的那个空会导致复杂度偏高,因此只要简单地找到可填数数目相对小的那个空就行)。
找一下搜索面临或有关的状态与维度:当前分数v,当前要填的点的坐标,填了t个点,总共要填cnt个点,各行各列各九宫格填数情况。
可行性剪枝考虑:
发现一个有趣的性质:数独中每一行都会有1到9各出现一次,共有9行,所以1到9每个数到最后必定出现9次,所以可以设一个used[]数组记录数出现的次数,若小于0,肯定不是正解,回溯,故每次填空要用不仅usedi大于0且i在同行同列同九宫格都没有出现过的i。
最优性剪枝:
可以将所有空能填的最大的数*该空的分数求和得到一个还能得到的至多的分数zhiduo,填空时,若发现v+zhiduo还<=已搜到的答案ans,显然不会使答案更优,所以回溯;同时每填一个空都更新一下zhiduo,回溯时也要照顾到zhiduo。
AC代码:
1 #include<iostream> 2 #include<cstdio> 3 #include<cmath> 4 #include<algorithm> 5 6 using namespace std; 7 8 int a[9][9],fen[9][9],cnt,zhiduo,m[100][2],ans;//第二下标: 0:纵坐标;1:横坐标 先纵再横 9 int ma[10][10],used[10]; 10 int h[10],l[10],maxh,maxl;//某行、列已经填了几个数 11 12 bool hang[10][10],lie[10][10],jiu[3][3][10];//行、列、九宫格的填数情况 13 14 void init() 15 { 16 for(int i=1;i<=9;i++) used[i]=9; 17 for(int f=6;f<=10;f++)//初始化每个空的分数,不懂的可以拿纸笔自己模拟一下 18 { 19 for(int i=f-6;i<=8-f+6;i++) fen[f-6][i]=fen[8-f+6][i]=f; 20 for(int j=f-6+1;j<=8-f+6-1;j++) fen[j][f-6]=fen[j][8-f+6]=f; 21 } 22 } 23 24 void dfs(int k,int v,int x,int y)//要填第k个,当前分数v,当前填的空的纵、横坐标。 25 { 26 if(v+zhiduo<=ans) return;//最优性剪枝 27 for(int i=1;i<=9;i++)//填哪个数 28 { 29 if(used[i]&&(!hang[x][i])&&(!lie[y][i])&&(!jiu[x/3][y/3][i]))//可行性剪枝 30 { 31 v+=fen[x][y]*i; 32 if(k!=cnt) 33 { 34 used[i]--; 35 hang[x][i]=1;lie[y][i]=1;jiu[x/3][y/3][i]=1; 36 h[x]++;l[y]++; 37 zhiduo-=fen[x][y]*ma[x][y]; 38 maxh=maxl=9; 39 a[x][y]=i; 40 for(int i=0;i<=8;i++) if(h[i]!=9&&h[i]>h[maxh]) maxh=i;//找接下来要填的空 41 for(int i=0;i<=8;i++) if(!a[maxh][i]&&l[i]!=9&&l[i]>l[maxl]) maxl=i; 42 dfs(k+1,v,maxh,maxl); 43 hang[x][i]=0;lie[y][i]=0;jiu[x/3][y/3][i]=0;//回溯的时候少一个都会wa啊 44 zhiduo+=fen[x][y]*ma[x][y]; 45 h[x]--;l[y]--; 46 a[x][y]=0; 47 used[i]++; 48 } 49 else 50 ans=max(ans,v); 51 v-=fen[x][y]*i; 52 } 53 } 54 } 55 56 int main() 57 { 58 init(); 59 int v=0; 60 for(int i=0;i<=8;i++) 61 for(int j=0;j<=8;j++) 62 { 63 scanf("%d",&a[i][j]); 64 if(a[i][j]) 65 { 66 used[a[i][j]]--; 67 h[i]++; 68 l[j]++; 69 if(hang[i][a[i][j]]||lie[j][a[i][j]]||jiu[i/3][j/3][a[i][j]]) 70 { 71 cout<<-1; 72 return 0; 73 } 74 hang[i][a[i][j]]=1; 75 lie[j][a[i][j]]=1; 76 jiu[i/3][j/3][a[i][j]]=1; 77 v+=fen[i][j]*a[i][j]; 78 } 79 else 80 cnt++; 81 } 82 int most; 83 for(int i=0;i<=8;i++)//求起始时的至多zhiduo 84 for(int j=0;j<=8;j++) 85 if(!a[i][j]) 86 { 87 most=9; 88 while(hang[i][most]||lie[j][most]||jiu[i/3][j/3][most]) most--; 89 ma[i][j]=most;//空(j,i)能填的最大的数 90 zhiduo+=most*fen[i][j]; 91 } 92 h[9]=-165123; 93 l[9]=-151562; 94 maxh=9,maxl=9; 95 for(int i=0;i<=8;i++)//找一开始要填的空 96 if(h[i]!=9&&h[i]>h[maxh]) maxh=i; 97 for(int i=0;i<=8;i++) 98 if(!a[maxh][i]&&l[i]!=9&&l[i]>l[maxl]) maxl=i; 99 dfs(1,v,maxh,maxl); 100 if(!ans) ans=-1; 101 cout<<ans; 102 return 0; 103 }