P1074 靶形數獨題解
題目描述
小城和小華都是熱愛數學的好學生,最近,他們不約而同地迷上了數獨遊戲,好勝的他們想用數獨來一比高低。但普通的數獨對他們來說都過於簡單了,於是他們向 Z 博士請教,Z 博士拿出了他最近發明的“靶形數獨”,作為這兩個孩子比試的題目。
靶形數獨的方格同普通數獨一樣,在 9 格寬×9 格高的大九宮格中有9 個 3 格寬×3 格高的小九宮格(用粗黑色線隔開的)。在這個大九宮格中,有一些數字是已知的,根據這些數字,利用邏輯推理,在其他的空格上填入 1 到 9的數字。每個數字在每個小九宮格內不能重復出現,每個數字在每行、每列也不能重復出現。但靶形數獨有一點和普通數獨不同,即每一個方格都有一個分值,而且如同一個靶子一樣,離中心越近則分值越高。(如圖)
上圖具體的分值分布是:最裏面一格(黃色區域)為 10 分,黃色區域外面的一圈(紅色區域)每個格子為9分,再外面一圈(藍色區域)每個格子為8 分,藍色區域外面一圈(棕色區域)每個格子為7分,最外面一圈(白色區域)每個格子為6分,如上圖所示。比賽的要求是:每個人必須完成一個給定的數獨(每個給定數獨可能有不同的填法),而且要爭取更高的總分數。而這個總分數即每個方格上的分值和完成這個數獨時填在相應格上的數字的乘積的總和。
總分數即每個方格上的分值和完成這個數獨時填在相應格上的數字的乘積的總和。如圖,在以下的這個已經填完數字的靶形數獨遊戲中,總分數為 2829。遊戲規定,將以總分數的高低決出勝負。
由於求勝心切,小城找到了善於編程的你,讓你幫他求出,對於給定的靶形數獨,能夠得到的最高分數。
輸入輸出格式
輸入格式:
一共 9 行。每行9個整數(每個數都在0−9 的範圍內),表示一個尚未填滿的數獨方格,未填的空格用“0”表示。每兩個數字之間用一個空格隔開。
輸出格式:
輸出共 1 行。輸出可以得到的靶形數獨的最高分數。如果這個數獨無解,則輸出整數−1。
輸入輸出樣例
輸入樣例#1:
7 0 0 9 0 0 0 0 1 1 0 0 0 0 5 9 0 0 0 0 0 2 0 0 0 8 0 0 0 5 0 2 0 0 0 3 0 0 0 0 0 0 6 4 8 4 1 3 0 0 0 0 0 0 0 0 7 0 0 2 0 9 0 2 0 1 0 6 0 8 0 4 0 8 0 5 0 4 0 1 2
輸出樣例#1:
2829
輸入樣例#2:
0 0 0 7 0 2 4 5 3
9 0 0 0 0 8 0 0 0
7 4 0 0 0 5 0 1 0
1 9 5 0 8 0 0 0 0
0 7 0 0 0 0 0 2 5
0 3 0 5 7 9 1 0 8
0 0 0 6 0 1 0 0 0
0 6 0 9 0 0 0 0 1
0 0 0 0 0 0 0 0 6
輸出樣例#2:
2852
說明
【數據範圍】
40%的數據,數獨中非 0 數的個數不少於30。
80%的數據,數獨中非 0 數的個數不少於26。
100%的數據,數獨中非0數的個數不少於24。
NOIP 2009 提高組 第四題
這真是一道惡心的搜索題,首先你得先熟悉數獨。
玩家需要根據9×9盤面上的已知數字,推理出所有剩余空格的數字,並滿足每一行、每一列、每一個粗線宮(3*3)內的數字均含1-9,不重復。
————百度百科
所以我們有三個數組分別代表行,列,九宮格中數字使用情況。
行和列都還好,循環判斷即可,但九宮格,就需要一個函數
1 int ninth( int i , int j ) { 2 if( i <= 3 && j <= 3 ) return 1 ; 3 if( i <= 3 && j <= 6 ) return 2 ; 4 if( i <= 3 ) return 3 ; 5 if( i <= 6 && j <= 3 ) return 4 ; 6 if( i <= 6 && j <= 6 ) return 5 ; 7 if( i <= 6 ) return 6 ; 8 if( j <= 3 ) return 7 ; 9 if( j <= 6 ) return 8 ; 10 return 9 ; 11 }
接著我們處理每個格子的分數,我是用函數判斷,而ly用數組,最後時間來看,我還是太蒟了
1 inline int check(int x,int y) 2 { 3 if(x==1||y==1||x==9||y==9)return 6; 4 if(x==2||y==2||x==8||y==8)return 7; 5 if(x==3||y==3||x==7||y==7)return 8; 6 if(x==4||y==4||x==6||y==6)return 9; 7 if(x==5&&y==5)return 10; 8 } ————by cx
1 int point[ 6 ] = { 0 , 6 , 7 , 8 , 9 , 10} ; 2 ————by ly
預處理完後便可以開始考慮如何搜索,這裏我與ly有了不同的搜法。
1.ly
ly選擇的是一行一行搜下去,全部答案搜出來後,再求最大值。
1 int dfs( int h , int x , int y ) { 2 if( h == 10 ) { 3 print( ); 4 return 0 ; 5 } 6 if( y == 10 ) { 7 dfs( h + 1 , sss[ h + 1 ].line , 1 ); 8 return 0 ; 9 } 10 if( !map[ x ][ y ] ) { 11 for( int i = 1 ; i <= 9 ; ++i ) { 12 if( line[ x ][ i ] == 0 && list[ y ][ i ] == 0 && nine[ ninth( x , y ) ][ i ] == 0 ){ 13 line[ x ][ i ] = 1 , list[ y ][ i ] = 1 , nine[ ninth( x , y ) ][ i ] = 1 ; 14 map[ x ][ y ] = i ; 15 dfs( h , x , y + 1 ); 16 map[ x ][ y ] = 0 ; 17 line[ x ][ i ] = 0 , list[ y ][ i ] = 0 , nine[ ninth( x , y ) ][ i ] = 0 ; 18 } 19 } 20 } 21 else dfs( h , x , y + 1 ); 22 }
2.cx
我選擇的是用一個數組存要填的的點,一個一個搜,我一開始以為我的方法會快一點,結果我被打臉了
1 void dfs(int x,int y) 2 { 3 if(tot==pos-1){maxn=max(ans,maxn);return;} 4 for(register int i=1;i<=9;++i) 5 { 6 if(!line[x][i]&&!list[y][i]&&!nine[ninth(x,y)][i]) 7 { 8 pos++;ans+=i*check(x,y); 9 line[x][i]=list[y][i]=1; 10 nine[ninth(x,y)][i]=1; 11 dfs(b[0][pos].w,b[1][pos].w); 12 pos--;ans-=i*check(x,y); 13 line[x][i]=list[y][i]=0; 14 nine[ninth(x,y)][i]=0; 15 } 16 } 17 }
接下來便是重點,搜索剪枝
像我這種沒玩過數獨的鄉裏人,不知道玩數獨有這樣一個方法:
從數多的一行開始填,這樣要選擇的數就少了,不合法的情況就可以省掉一些
所以我們定義一個(struck),用一個來存每行的個數。
用它作為關鍵字一遍後,再從最少的開始搜。
ly程序:(用時: 2678ms / 內存: 920KB)
1 #include<iostream> 2 #include<cstdio> 3 #include<algorithm> 4 using namespace std ; 5 6 inline int read( ) {//日常快讀 7 int x = 0 , f = 1 ; 8 char c = getchar( ) ; 9 while( c > ‘9‘ || c < ‘0‘ ) { 10 if( c == ‘-‘ ) f = -1 ; 11 c = getchar( ); 12 } 13 while( c >= ‘0‘ && c <= ‘9‘ ) x = x * 10 + c - ‘0‘ , c = getchar( ) ; 14 return f == 1 ? x : -x ; 15 } 16 17 int ninth( int i , int j ) {//判斷i行j列在第幾個九宮格裏,因為九個九宮格不重復,所以某些地方可以少判斷一些條件。 18 if( i <= 3 && j <= 3 ) return 1 ; 19 if( i <= 3 && j <= 6 ) return 2 ; 20 if( i <= 3 ) return 3 ; 21 if( i <= 6 && j <= 3 ) return 4 ; 22 if( i <= 6 && j <= 6 ) return 5 ; 23 if( i <= 6 ) return 6 ; 24 if( j <= 3 ) return 7 ; 25 if( j <= 6 ) return 8 ; 26 return 9 ; 27 } 28 29 int point[ 6 ] = { 0 , 6 , 7 , 8 , 9 , 10} ;//存放分數; 30 int map[ 10 ][ 10 ] ;//記錄某個位置上的數; 31 long long ans = -1 ;//無解則輸出-1; 32 struct node { 33 int line , sum ; 34 }sss[ 11 ] ;//記錄每行需要填的零的個數; 35 bool cmp( node i , node j ) { 36 return i.sum < j.sum ; 37 } 38 bool line[ 10 ][ 10 ] , nine[ 10 ][ 10 ] , list[ 10 ][ 10 ] ;//進行數獨遊戲的判斷; 39 //為了方便觀察,函數都扔下去; 40 int dfs( int , int , int ); 41 int print( ); 42 43 int main( ) 44 { 45 for( int i = 1 ; i <= 9 ; ++i ) { 46 int k = 0 ; 47 for( int j = 1 ; j <= 9 ; ++j ) { 48 map[ i ][ j ] = read( ) ; 49 if( !map[ i ][ j ] ) ++k; 50 line[ i ][ map[ i ][ j ] ] = 1 ; 51 nine[ ninth( i , j ) ][ map[ i ][ j ] ] = 1 ; 52 list[ j ][ map[ i ][ j ] ] = 1 ; 53 } 54 sss[ i ].sum = k , sss[ i ].line = i ; 55 } 56 sort( sss + 1 , sss + 10 , cmp ); 57 dfs( 1 , sss[ 1 ].line , 1 ) ; 58 printf( "%lld" , ans ); 59 return 0 ; 60 } 61 int dfs( int h , int x , int y ) { 62 if( h == 10 ) {//全部搜完了並成立,進行算分 63 print( ); 64 return 0 ; 65 } 66 if( y == 10 ) {//為避免特判過多而加的中轉; 67 dfs( h + 1 , sss[ h + 1 ].line , 1 ); 68 return 0 ; 69 } 70 if( !map[ x ][ y ] ) { 71 for( int i = 1 ; i <= 9 ; ++i ) { 72 if( line[ x ][ i ] == 0 && list[ y ][ i ] == 0 && nine[ ninth( x , y ) ][ i ] == 0 ){ 73 line[ x ][ i ] = 1 , list[ y ][ i ] = 1 , nine[ ninth( x , y ) ][ i ] = 1 ; 74 map[ x ][ y ] = i ; 75 dfs( h , x , y + 1 ); 76 //記得回溯 77 map[ x ][ y ] = 0 ; 78 line[ x ][ i ] = 0 , list[ y ][ i ] = 0 , nine[ ninth( x , y ) ][ i ] = 0 ; 79 } 80 } 81 } 82 else dfs( h , x , y + 1 ); 83 } 84 int print( ) {//統計當前方案的分數 85 long long sum = 0 ; 86 for( int i = 1 ; i <= 9 ; ++i ) { 87 for( int j = 1 ; j <= 9 ; ++j ) { 88 sum += ( map[ i ][ j ] * point[ min( min( i , 10 - i ) , min ( j , 10 - j ) ) ] ); 89 //越靠近中心,x與y越接近5,否則遠離5 90 //所以min( min( i , 10 - i ) , min ( j , 10 - j ) )與 91 //point數組搭配即可算出當前位置的分值 92 } 93 } 94 ans = max( ans , sum );//更新最大值 95 }
cx程序:(用時: 3361ms / 內存: 928KB)
1 #include<iostream> 2 #include<cstring> 3 #include<cstdio> 4 #include<algorithm> 5 using namespace std; 6 inline int max(int x,int y){return(x)<(y)?(y):(x);} 7 bool line[10][10],list[10][10],nine[10][10]; 8 int ans=0,tot=0,maxn=-1,pos=1,Line=0,a[10]; 9 //a數組記錄每行0的個數,maxn記錄最大值,tot記錄總共要填的點數 10 struct node 11 {int w,list;}b[2][82];//list記錄那行0的個數 12 inline void read(int &x) 13 { 14 x=0;int f=1; 15 char ch=getchar(); 16 while(ch<‘0‘||ch>‘9‘) 17 {if(ch==‘-‘) f=-1; ch=getchar();} 18 while(ch>=‘0‘&&ch<=‘9‘) 19 {x=x*10+ch-‘0‘;ch=getchar();} 20 x*=f; 21 } 22 inline void write(int x) 23 { 24 if(x<0){putchar(‘-‘);write(~x+1);} 25 else{if(x>9)write(x/10);putchar(x%10+‘0‘);} 26 } 27 inline int ninth(int i,int j) 28 { 29 if(i<=3&&j<=3)return 1;if(i<=3&&j<=6)return 2;if(i<=3)return 3; 30 if(i<=6&&j<=3)return 4;if(i<=6&&j<=6)return 5;if(i<=6)return 6; 31 if(j<=3)return 7;if(j<=6)return 8;return 9; 32 } 33 inline int check(int x,int y) 34 { 35 if(x==1||y==1||x==9||y==9)return 6; 36 if(x==2||y==2||x==8||y==8)return 7; 37 if(x==3||y==3||x==7||y==7)return 8; 38 if(x==4||y==4||x==6||y==6)return 9; 39 if(x==5&&y==5)return 10; 40 } 41 void dfs(int x,int y) 42 { 43 if(tot==pos-1){maxn=max(ans,maxn);return;}//搜完了 44 for(register int i=1;i<=9;++i)//判斷數字1-9 45 { 46 if(!line[x][i]&&!list[y][i]&&!nine[ninth(x,y)][i])//判斷數字是否被填過 47 { 48 pos++;ans+=i*check(x,y); 49 line[x][i]=list[y][i]=1; 50 nine[ninth(x,y)][i]=1; 51 dfs(b[0][pos].w,b[1][pos].w); 52 pos--;ans-=i*check(x,y); 53 line[x][i]=list[y][i]=0; 54 nine[ninth(x,y)][i]=0;//回溯 55 } 56 } 57 } 58 bool cmp(node i,node j) 59 {return i.list<j.list;} 60 int main() 61 { 62 bool flag1=0,flag2=0; 63 for(register int i=1;i<=9;++i) 64 { 65 for(register int j=1;j<=9;++j) 66 { 67 register int k;read(k); 68 if(!k)b[0][tot+1].w=i,b[1][tot+1].w=j,tot++,Line++;//代表要填 69 else//代表填過 70 { 71 if(i==1||i==2)if(j==4)if(k==9||k==2)flag1=flag2=1; 72 if((i==3&&j==4&&k==1))flag1=1;if(i==4&&j==4&&k==9)flag2=1; 73 ans+=k*check(i,j);nine[ninth(i,j)][k]=1; 74 line[i][k]=1,list[j][k]=1; 75 } 76 } 77 a[i]=Line;Line=0;//記錄每行0的個數 78 } 79 if(!flag1&&!flag2)//特判,為什麽後面講了 80 { 81 for(register int i=1;i<=tot;++i)b[0][i].list=b[1][i].list=a[b[0][i].w]; 82 std::sort(b[0]+1,b[0]+tot+1,cmp); 83 std::sort(b[1]+1,b[1]+tot+1,cmp);//按行中0的個數排序 84 } 85 dfs(b[0][pos].w,b[1][pos].w);//搜索 86 write(maxn); 87 }
因為我的搜法不同,在之後仍有兩個點過不去,所以我特判了一下不的情況,仔細反思一下,我好像懂了。
ly是一行行搜,所以他一直搜同一行,而我存的是點,又因為不穩定,我的程序可能搜完一個,又去搜另一行,所以不行,興許可以改成 或桶排,相較之下,很明顯還是ly程序更優。
P1074 靶形數獨題解