poj3076 Sudoku(DFS+剪枝)
阿新 • • 發佈:2018-12-10
題意
用A~P填寫一個16*16的數獨。
題解
DFS+超強剪枝 1、搜尋每一個位置可以填的數,如果只有一個,立刻填上;如果沒有可以填的數,立刻回溯。 2、列舉一個數字,在每個行\列\宮格中,有沒有可以填的地方。如果只有一個,將其填上;如果無法填上,立刻回溯。 3、選取一個可能情況最少的格子,列舉其所有情況。 4、dfs(k+1),重複執行以上操作。
以上1和2的操作是有區別的:1只有當一個格子所處的行\列\宮格已經把除X外的數字填過時,才會將其填上,它們是直接影響到這個格子的;2是說除了這個格子外,(同一行\列\宮格中)其它的格子由於某種原因,都不能填X,只有這個格子可以填時,就可以填了。 對於每個位置可填的數,我們用一個二進位制數來記錄,這樣能優化常數。
程式碼
#include<cstdio> #include<cstring> #include<algorithm> using namespace std; const int maxn=20; int map[maxn][maxn]; unsigned short table[maxn][maxn];//talbe[i][j]表示(i,j)可以填的數有哪些,0可填,1不可填 int filled; void fill(int x,int y,int a)//(x,y)填a { filled++; map[x][y]=a; table[x][y]|=1<<a-1;// for(int i=0;i<16;i++) table[x][i]|=1<<a-1,table[i][y]|=1<<a-1; int r=x/4*4,c=y/4*4; for(int i=0;i<4;i++) for(int j=0;j<4;j++) table[r+i][c+j]|=1<<a-1; } int count_one(unsigned short x)//判斷x中1的個數是否只有1個 { for(int i=0;x;i++) { if(x&1) { if(x>>1==0) return i; return -1; } x>>=1; } return -1; } int hang(int x,int k)//第x行,數字k+1。返回>0表示唯一可填k+1的位置;返回-1表示出現超過1次;返回-2表示k+1沒有可以填的位置 { int p=-1; for(int y=0;y<16;y++) { if(map[x][y]==k+1) return -1;//數字k+1已填 if(map[x][y]>0) continue; if((table[x][y]&1<<k)==0) { if(p!=-1) return -1;//出現超過1次 p=y; } } if(p!=-1) return p; return -2; } int lie(int y,int k)//第y行,數字k { int p=-1; for(int x=0;x<16;x++) { if(map[x][y]==k+1) return -1; if(map[x][y]>0) continue; if((table[x][y]&1<<k)==0) { if(p!=-1) return -1; p=x; } } if(p!=-1) return p; return -2; } void gong(int r,int c,int k,int &x,int &y)//以(r,c)為左上角的宮格,數字k+1,(x,y)為唯一可填座標 { x=-2; for(int i=0;i<4;i++) for(int j=0;j<4;j++) { if(map[r+i][c+j]==k+1){x=-1;return ;} if(map[r+i][c+j]>0) continue; if((table[r+i][c+j]&1<<k)==0) { if(x!=-2){x=-1;return ;} x=i;y=j; } } } int count_1(unsigned short x)//求x中1的個數 { int re=0; while(x) { if(x&1) re++; x>>=1; } return re; } bool search() { if(filled==256) return true; //只有一個數字可以填的格子先處理 for(int x=0;x<16;x++) for(int y=0;y<16;y++) { if(map[x][y]>0) continue; int k=count_one(table[x][y]); if(k!=-1) fill(x,y,k+1); } //數字k+1在行\列\宮格中可填情況 for(int x=0;x<16;x++) for(int k=0;k<16;k++) { int y=hang(x,k); if(y==-2) return false;//回溯 if(y!=-1) fill(x,y,k+1); } for(int y=0;y<16;y++) for(int k=0;k<16;k++) { int x=lie(y,k); if(x==-2) return false;//回溯 if(x!=-1) fill(x,y,k+1); } for(int r=0;r<16;r+=4) for(int c=0;c<16;c+=4) for(int k=0;k<16;k++) { int x,y; gong(r,c,k,x,y); if(x==-2) return false;//回溯 if(x!=-1) fill(r+x,c+y,k+1); } if(filled==256) return true; //備份 int t_filled; int t_map[maxn][maxn]; unsigned short t_table[maxn][maxn]; t_filled=filled; for(int i=0;i<16;i++) for(int j=0;j<16;j++) { t_map[i][j]=map[i][j]; t_table[i][j]=table[i][j]; } //找可能情況最少的格子來列舉 int mx,my,mn=16; for(int i=0;i<16;i++) for(int j=0;j<16;j++) { if(map[i][j]>0) continue; int r=16-count_1(table[i][j]); if(r<mn) { mn=r; mx=i;my=j; } } for(int k=0;k<16;k++) { if((table[mx][my]&1<<k)==0) { fill(mx,my,k+1); if(search()) return true; filled=t_filled; for(int i=0;i<16;i++) for(int j=0;j<16;j++) { map[i][j]=t_map[i][j]; table[i][j]=t_table[i][j]; } } } return false; } char ar[maxn]; int main() { while(1) { filled=0; memset(map,0,sizeof(map)); memset(table,0,sizeof(table)); for(int i=0;i<16;i++) { if(scanf("%s",ar)==EOF) return 0; for(int j=0;j<16;j++) { if(ar[j]!='-') fill(i,j,ar[j]-'A'+1); } } search(); for(int i=0;i<16;i++) { for(int j=0;j<16;j++) printf("%c",map[i][j]+'A'-1); printf("\n"); } printf("\n"); } return 0; }