1.數(shù)獨(dú)題目傳送門:https://www.acwing.com/problem/content/168/
2.靶形數(shù)獨(dú)題目傳送門:https://www.acwing.com/problem/content/185/
題目1是一個(gè)普通的數(shù)獨(dú),并且測(cè)試數(shù)據(jù)保證有解,但是測(cè)試數(shù)據(jù)是多組,在搜索上不講技巧的搜索是會(huì)TLE的,就連bitset去處理狀態(tài)都會(huì)。
題目2是在一個(gè)普通的數(shù)獨(dú)的基礎(chǔ)上加了兩點(diǎn)不同,第一不同是數(shù)據(jù)里有沒有解的情況,第二不同是在九宮格上還有權(quán)值,要在所有方案中尋找最優(yōu)值,即找到一種方案還不行,需要在填滿后,按照規(guī)則每個(gè)格子需要乘上權(quán)值后求和,輸出最大的和值。那么需要把所有方案都找出并維護(hù)最大值。,還好這題數(shù)據(jù)里只有一個(gè)需要解決的數(shù)獨(dú)問題。
那么把題目1的數(shù)據(jù)求解后,再做第二題就顯得比較容易了。
那么對(duì)于一個(gè)普通的數(shù)獨(dú)問題,我們采用如何的策略去填數(shù)獨(dú)呢?
1.找橫、豎和小九宮格中空白處能填的“合法數(shù)字”個(gè)數(shù)最少的格子去填,我想你如果手動(dòng)填數(shù)獨(dú)也會(huì)是這個(gè)策略。如果有最少可以選擇的數(shù)的空白格里可以填的數(shù)有多個(gè),暴力枚舉這幾個(gè)數(shù)搜索回溯,而不是隨意找個(gè)空白格去填。
2.如何快速確定每個(gè)位置上能填的合法數(shù)字呢,等到要找時(shí),再去一遍掃行,一遍掃列,一遍掃九宮格尋找,顯然速度就會(huì)慢了些。
在此我們可以運(yùn)用位運(yùn)算來進(jìn)行統(tǒng)計(jì)。具體方法是:
- 對(duì)于每行、每列、每個(gè)九宮格的3個(gè)二進(jìn)制數(shù)(全局整數(shù)變量)保存哪些數(shù)字可以填。
- 對(duì)于每個(gè)位置,把它所在行、所在列和所在小九宮格的3個(gè)二進(jìn)制數(shù)進(jìn)行位與運(yùn)算,就可以看到哪些數(shù)字是可以填的。如,假設(shè)(1,1)這個(gè)位置的行上的狀態(tài)是100111000,列上的狀態(tài)是011011011,小九宮格上的狀態(tài)是110111100,那么這個(gè)位上可以用的數(shù)有2個(gè),分別是4,5.因?yàn)槲慌c運(yùn)算后從右往左數(shù)第4個(gè)位上和第5個(gè)位上是1,就表示這兩個(gè)數(shù)沒有出現(xiàn)過。程序?qū)崿F(xiàn)時(shí)可以參考lowbit,取最低位的1采用(x & -x)的方式。
100111000
011011011
&110111100
--------
000011000 - 當(dāng)一個(gè)位置上嘗試放入一個(gè)可以合法的數(shù)后,把該位置的行列和小九宮格的狀態(tài)該位置上的數(shù)置為0,回溯時(shí)改回1即可還原現(xiàn)場(chǎng)。
題目1具體代碼是:
//同樣的思路用bitset去處理狀態(tài)超時(shí),用位運(yùn)算就AC了,可見bitiset在處理二進(jìn)制數(shù)做
//狀態(tài)時(shí)在速度上稍顯不足。
#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(); //找空白位置上可以放置的數(shù)據(jù)選擇性最小的哪個(gè)坐標(biāo)點(diǎn)。 int no = rcno[p.x][p.y];int x = p.x,y = p.y;int vs = row[x] & col[y] & rcsmall[no];//vs存儲(chǔ)的是二進(jìn)制位上是1的對(duì)應(yīng)數(shù)可選。 for( ; vs ; vs = vs - (vs & -vs)){//通過尋找最低位為1的位置,逐漸枚舉每個(gè)可以放入的數(shù)。 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(){//預(yù)處理原始表中每行每列每個(gè)九宮格中數(shù)字的使用狀態(tài)。 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(){ //預(yù)處理每個(gè)數(shù)上對(duì)應(yīng)的二進(jìn)制數(shù)中有幾個(gè)1,用于查找最少合法數(shù)據(jù)的位置時(shí)用。 for(int i =0; i< (1 << 9);i++)for(int j = i; j; j = j - (j & -j))onenum[i] ++;//預(yù)處理數(shù)的二進(jìn)制數(shù)中只有一個(gè)1時(shí),它代表的時(shí)哪個(gè)數(shù)。 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,需要做以下更改:
- 輸入方式不一樣。
- 增加一個(gè)權(quán)值數(shù)組。
- 對(duì)出現(xiàn)填滿時(shí)由輸出改為對(duì)數(shù)值的計(jì)算并維護(hù)。
- 出現(xiàn)填滿的方案時(shí),需要繼續(xù)尋找下面的其他方案,因此去掉函數(shù)中的返回true,false之類的。
- 在出現(xiàn)方案時(shí)立個(gè)flag,帶搜索完畢檢查其flag的狀態(tài)來確定是否有解。
具體代碼如下:
#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(); //找空白位置上可以放置的數(shù)據(jù)選擇性最小的哪個(gè)坐標(biāo)點(diǎn)。 int no = rcno[p.x][p.y];int x = p.x,y = p.y;int vs = row[x] & col[y] & rcsmall[no];//vs存儲(chǔ)的是二進(jìn)制位上是1的對(duì)應(yīng)數(shù)可選。 for( ; vs ; vs = vs - (vs & -vs)){//通過尋找最低位為1的位置,逐漸枚舉每個(gè)可以放入的數(shù)。 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了,也終于很快完成了靶形數(shù)獨(dú),在幾年前不敢寫的程序現(xiàn)在看來也不過如此,確實(shí)成長了,但是速度嘛不言而喻,就像我跑馬拉松,人家早就在4小時(shí)內(nèi)到達(dá)了終點(diǎn),我設(shè)定的目標(biāo)是6小時(shí)內(nèi)完賽,享受沿途風(fēng)景,感受途中心情變化,沒人催,就靠點(diǎn)自律,自己感動(dòng)自己,跑起來自然那就叫一個(gè)慢!
總結(jié)
以上是生活随笔為你收集整理的ACwing166数独与183靶形数独的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。