ACwing166數獨與183靶形數獨

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小時內完賽,享受沿途風景,感受途中心情變化,沒人催,就靠點自律,自己感動自己,跑起來自然那就叫一個慢!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章