1. 程式人生 > >poj3076 Sudoku(DFS+剪枝)

poj3076 Sudoku(DFS+剪枝)

題意

用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;
}