1.數獨題目傳送門:https://www.acwing.com/problem/content/168/
2.靶形數獨題目傳送門:https://www.acwing.com/problem/content/185/
題目1是一個普通的數獨,并且測試數據保證有解,但是測試數據是多組,在搜索上不講技巧的搜索是會TLE的,就連bitset去處理狀態都會。
題目2是在一個普通的數獨的基礎上加了兩點不同,第一不同是數據里有沒有解的情況,第二不同是在九宮格上還有權值,要在所有方案中尋找最優值,即找到一種方案還不行,需要在填滿后,按照規則每個格子需要乘上權值后求和,輸出最大的和值。那么需要把所有方案都找出并維護最大值。,還好這題數據里只有一個需要解決的數獨問題。
那么把題目1的數據求解后,再做第二題就顯得比較容易了。
那么對于一個普通的數獨問題,我們采用如何的策略去填數獨呢?
1.找橫、豎和小九宮格中空白處能填的“合法數字”個數最少的格子去填,我想你如果手動填數獨也會是這個策略。如果有最少可以選擇的數的空白格里可以填的數有多個,暴力枚舉這幾個數搜索回溯,而不是隨意找個空白格去填。
2.如何快速確定每個位置上能填的合法數字呢,等到要找時,再去一遍掃行,一遍掃列,一遍掃九宮格尋找,顯然速度就會慢了些。
在此我們可以運用位運算來進行統計。具體方法是:
- 對于每行、每列、每個九宮格的3個二進制數(全局整數變量)保存哪些數字可以填。
- 對于每個位置,把它所在行、所在列和所在小九宮格的3個二進制數進行位與運算,就可以看到哪些數字是可以填的。如,假設(1,1)這個位置的行上的狀態是100111000,列上的狀態是011011011,小九宮格上的狀態是110111100,那么這個位上可以用的數有2個,分別是4,5.因為位與運算后從右往左數第4個位上和第5個位上是1,就表示這兩個數沒有出現過。程序實現時可以參考lowbit,取最低位的1采用(x & -x)的方式。
100111000
011011011
&110111100
--------
000011000 - 當一個位置上嘗試放入一個可以合法的數后,把該位置的行列和小九宮格的狀態該位置上的數置為0,回溯時改回1即可還原現場。
題目1具體代碼是:
//同樣的思路用bitset去處理狀態超時,用位運算就AC了,可見bitiset在處理二進制數做
//狀態時在速度上稍顯不足。
#include<bits/stdc++.h>
using namespace std;
int sd[10][10],cnt =0;
int row[9],col[9],rcsmall[9];
int num[513],onenum[513];
char c;
int rcno[10][10]={{0,0,0,0,0,0,0,0,0,0},{0,1,1,1,2,2,2,3,3,3},{0,1,1,1,2,2,2,3,3,3},{0,1,1,1,2,2,2,3,3,3},{0,4,4,4,5,5,5,6,6,6},{0,4,4,4,5,5,5,6,6,6},{0,4,4,4,5,5,5,6,6,6},{0,7,7,7,8,8,8,9,9,9},{0,7,7,7,8,8,8,9,9,9},{0,7,7,7,8,8,8,9,9,9},};
struct pos{int x,y;
};
void print(){for(int i = 1;i<= 9 ;i++){for(int j =1; j<= 9; j++){printf("%d",sd[i][j]);}}printf("\n");
}
pos find(){int minn =10;pos temp;temp.x = -1,temp.y = -1;for(int i =1;i<= 9;i++){for(int j = 1;j<= 9; j++){if(sd[i][j]==0){int no = rcno[i][j];int vs = row[i] & col[j] & rcsmall[no]; if(minn >onenum[vs]){minn = onenum[vs];temp.x = i;temp.y = j;}}}}return temp;
}
bool dfs(int k){if(k == cnt+1){print();return true;}pos p = find(); //找空白位置上可以放置的數據選擇性最小的哪個坐標點。 int no = rcno[p.x][p.y];int x = p.x,y = p.y;int vs = row[x] & col[y] & rcsmall[no];//vs存儲的是二進制位上是1的對應數可選。 for( ; vs ; vs = vs - (vs & -vs)){//通過尋找最低位為1的位置,逐漸枚舉每個可以放入的數。 int t =num[vs & -vs];row[x] ^= 1<< (t-1);col[y] ^= 1<< (t-1);rcsmall[no] ^= 1<< (t-1); sd[x][y] = t;if(dfs(k+1)) return true;sd[x][y] = 0;row[x] ^= 1<< (t-1);col[y] ^= 1<< (t-1);rcsmall[no] ^= 1<< (t-1); }return false;
}
void init(){for(int i = 0;i<10;i++){row[i] = (1<<9) - 1;col[i]= (1<<9) - 1;rcsmall[i]= (1<<9) - 1;}
}
void read(){c = getchar();if(c == 'e') return;sd[1][1] = c =='.' ? 0: c-48;for(int i = 2;i<= 81; i++){c = getchar();sd[(i-1)/9 +1][(i-1) % 9+1] = c =='.' ? 0: c-48;}c = getchar(); //?üê???DD?£
}
void pre(){//預處理原始表中每行每列每個九宮格中數字的使用狀態。 for(int i = 1;i<= 9 ;i++){for(int j =1; j<= 9; j++){int no = rcno[i][j];if(sd[i][j] != 0){row[i] ^= 1<< (sd[i][j]-1);col[j] ^= 1<< (sd[i][j]-1);rcsmall[no] ^= 1<< (sd[i][j]-1); }else{cnt ++;}} }
}
int main(){ //預處理每個數上對應的二進制數中有幾個1,用于查找最少合法數據的位置時用。 for(int i =0; i< (1 << 9);i++)for(int j = i; j; j = j - (j & -j))onenum[i] ++;//預處理數的二進制數中只有一個1時,它代表的時哪個數。 for(int i = 1;i<= 9 ;i ++){num[1<< (i-1)] = i;}while(1){cnt = 0;init();read();if(c == 'e')break;pre(); dfs(1);}return 0;
}
題目二的題解相比題目1,需要做以下更改:
- 輸入方式不一樣。
- 增加一個權值數組。
- 對出現填滿時由輸出改為對數值的計算并維護。
- 出現填滿的方案時,需要繼續尋找下面的其他方案,因此去掉函數中的返回true,false之類的。
- 在出現方案時立個flag,帶搜索完畢檢查其flag的狀態來確定是否有解。
具體代碼如下:
#include<bits/stdc++.h>
using namespace std;
int sd[10][10],cnt =0;
int row[9],col[9],rcsmall[9];
int num[513],onenum[513];
int ans = 0;
char c;
bool flag = false;
int rcno[10][10]={{0,0,0,0,0,0,0,0,0,0},{0,1,1,1,2,2,2,3,3,3},{0,1,1,1,2,2,2,3,3,3},{0,1,1,1,2,2,2,3,3,3},{0,4,4,4,5,5,5,6,6,6},{0,4,4,4,5,5,5,6,6,6},{0,4,4,4,5,5,5,6,6,6},{0,7,7,7,8,8,8,9,9,9},{0,7,7,7,8,8,8,9,9,9},{0,7,7,7,8,8,8,9,9,9},};
int score[10][10]={{0,0,0,0,0,0,0,0,0,0},{0,6,6,6,6,6,6,6,6,6},{0,6,7,7,7,7,7,7,7,6},{0,6,7,8,8,8,8,8,7,6},{0,6,7,8,9,9,9,8,7,6},{0,6,7,8,9,10,9,8,7,6},{0,6,7,8,9,9,9,8,7,6},{0,6,7,8,8,8,8,8,7,6},{0,6,7,7,7,7,7,7,7,6},{0,6,6,6,6,6,6,6,6,6},
};
struct pos{int x,y;
};
void getPerfect(){int sum = 0;for(int i = 1;i<= 9 ;i++){for(int j =1; j<= 9; j++){sum += sd[i][j]*score[i][j];}}ans = max(ans,sum);
}
pos find(){int minn =10;pos temp;temp.x = -1,temp.y = -1;for(int i =1;i<= 9;i++){for(int j = 1;j<= 9; j++){if(sd[i][j]==0){int no = rcno[i][j];int vs = row[i] & col[j] & rcsmall[no]; if(minn >onenum[vs]){minn = onenum[vs];temp.x = i;temp.y = j;}}}}return temp;
}
void dfs(int k){if(k == cnt+1){flag = true;getPerfect();return;}pos p = find(); //找空白位置上可以放置的數據選擇性最小的哪個坐標點。 int no = rcno[p.x][p.y];int x = p.x,y = p.y;int vs = row[x] & col[y] & rcsmall[no];//vs存儲的是二進制位上是1的對應數可選。 for( ; vs ; vs = vs - (vs & -vs)){//通過尋找最低位為1的位置,逐漸枚舉每個可以放入的數。 int t =num[vs & -vs];row[x] ^= 1<< (t-1);col[y] ^= 1<< (t-1);rcsmall[no] ^= 1<< (t-1); sd[x][y] = t;dfs(k+1);sd[x][y] = 0;row[x] ^= 1<< (t-1);col[y] ^= 1<< (t-1);rcsmall[no] ^= 1<< (t-1); }
}
void init(){for(int i = 0;i<10;i++){row[i] = (1<<9) - 1;col[i]= (1<<9) - 1;rcsmall[i]= (1<<9) - 1;}
}
void read(){for(int i = 1;i<= 9;i++){for(int j = 1;j<= 9 ;j++){scanf("%d",&sd[i][j]);}}
}
void pre(){for(int i = 1;i<= 9 ;i++){for(int j =1; j<= 9; j++){int no = rcno[i][j];if(sd[i][j] != 0){row[i] ^= 1<< (sd[i][j]-1);col[j] ^= 1<< (sd[i][j]-1);rcsmall[no] ^= 1<< (sd[i][j]-1); }else{cnt ++;}} }
}
int main(){ for(int i =0; i< (1 << 9);i++)for(int j = i; j; j = j - (j & -j))onenum[i] ++;for(int i = 1;i<= 9 ;i ++){num[1<< (i-1)] = i;} cnt = 0;init();read();pre(); dfs(1);if(flag)cout << ans;else cout << "-1";return 0;
}
好吧,終于還是咬咬牙把前幾天一口氣用bitset+搜索的方式寫完且TLE程序給改AC了,也終于很快完成了靶形數獨,在幾年前不敢寫的程序現在看來也不過如此,確實成長了,但是速度嘛不言而喻,就像我跑馬拉松,人家早就在4小時內到達了終點,我設定的目標是6小時內完賽,享受沿途風景,感受途中心情變化,沒人催,就靠點自律,自己感動自己,跑起來自然那就叫一個慢!
總結
以上是生活随笔為你收集整理的ACwing166数独与183靶形数独的全部內容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。