avatar
fireworks99
keep hungry keep foolish

POJ Sudoku(数独) 2676 2918 3074 3076

Description

填补数独空白处

Analyze

对于2676 和 2918 两题来说,不需要复杂的剪枝,普通DFS即可过

Code

#include <cstdio>
#include <string>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;

int ans[12][12];
string s[12];
bool flag;

bool check (int x, int y, int num)
{
    for(int i = 1; i <= 9; ++i)
        if(ans[x][i] == num || ans[i][y] == num)
            return 0;
    int base_x = (x - 1) / 3 * 3;
    int base_y = (y - 1) / 3 * 3;
    int xx = x % 3, yy = y % 3;

    int cx = (xx + 1) % 3;
    int dx = (xx + 2) % 3;
    int cy = (yy + 1) % 3;
    int dy = (yy + 2) % 3;

    if(!cx)
        cx = 3;
    if(!dx)
        dx = 3;
    if(!cy)
        cy = 3;
    if(!dy)
        dy = 3;

    if(ans[base_x + cx][base_y + cy] == num
            || ans[base_x + cx][base_y + dy] == num
            || ans[base_x + dx][base_y + cy] == num
            || ans[base_x + dx][base_y + dy] == num)
        return 0;

    return 1;
}

void DFS(int x, int y)
{
    if(x == 10)
    {
        flag = 1;
        return ;
    }
    if(flag)
        return ;

    if(y > 9)
        DFS(x + 1, 1);//每次DFS完测一次flag:任意一次退出都可能是完成任务
    if(flag)
        return ;

    if(ans[x][y])
        DFS(x, y + 1);
    else//原先没把这个for放在else里
    {
        for(int i = 1; i <= 9; ++i)
            if(check(x, y, i))
            {
                ans[x][y] = i;
                DFS(x, y + 1);
                if(flag)
                    return ;
                ans[x][y] = 0;
            }
    }
    if(flag)
        return ;
}

int main()
{
    int _;
    scanf("%d", &_);
    while(_--)
    {
        flag = 0;
        for(int i = 0; i < 9; ++i)
            cin >> s[i];
        for(int i = 1; i <= 9; ++i)
            for(int j = 1; j <= 9; ++j)
                ans[i][j] = s[i - 1][j - 1] - '0';

        DFS(1, 1);
        for(int i = 1; i <= 9; ++i)
        {
            for(int j = 1; j <= 9; ++j)
                cout << ans[i][j];
            cout << '\n';
        }
    }
    return 0;
}

对于3074(数据强一些)

如果发现某行、某列、某九宫格可填的数字比较少,那么优先处理他们

可关键在于如何标记某行(或某列、某九宫格)有哪些可填数字呢?

可填数字总共9个,我们可以想到使用二进制进行状态压缩

用一个十进制数字的二进制形态的1/0位代表可填否

Code

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int INF = 0x3f3f3f3f;

char a[120];
char b[12][12];

int tot;//空的个数

int row[12];//第i行可填的数字(二进制状态压缩为一个十进制的int)
int col[12];
int room[12];

int cnt[520];
int num[520];

int get_room(int x, int y)
{
    return (x - 1) / 3 * 3 + (y - 1) / 3 + 1;
/*  1 4 7
    2 5 8
    3 6 9  */
}

//某个数字的存在引起该行该列该九宫格不可再填该数字
void flip(int x, int y, int z)
{
    row[x] ^= (1 << z);
    col[y] ^= (1 << z);
    room[get_room(x, y)] ^= (1 << z);
}


void init()
{
    //(1 << 9) - 1 == 2 ^ 10 - 1 == 11111111(二进制)
    //默认9个数字皆可填
    for(int i = 1; i <= 9; ++i)
        row[i] = col[i] = room[i] = (1 << 9) - 1;
    tot = 0;
    for(int i = 1; i <= 9; ++i)
        for(int j = 1; j <= 9; ++j)
        {
            b[i][j] = a[ (i - 1) * 9 + j - 1 ];
            if(b[i][j] != '.')
                flip(i, j, b[i][j] - '1');
            else
                tot++;
        }
}

bool DFS(int tot)
{
    if(tot == 0)
        return 1;
    int mmin = INF, x, y;
    for(int i = 1; i <= 9; ++i)
        for(int j = 1 ;j <= 9; ++j)
        {
            if(b[i][j] != '.')
                continue;

            int val = row[i] & col[j] & room[get_room(i, j)];
            if(val == 0)//当前格子可填数字的个数为0
                return 0;

            if(cnt[val] < mmin)
            {
                x = i, y = j;//这个格子可填数字的个数最少
                mmin = cnt[val];//可填数字的个数
            }
        }
    int val = row[x] & col[y] & room[get_room(x, y)];
    for(; val; val -= (val & -val))//之前那种方法不太行了....
    {
        int tem = num[val & -val];
        b[x][y] = tem + '1';
        flip(x, y, tem);
        if(DFS(tot - 1))
            return 1;
        flip(x, y, tem);//消除影响
        b[x][y] = '.';
    }
    return 0;
}

// (i & -i) == 2 ^ p   (p为i的最低位1所在位)

int main()
{
    //十进制数字i转为二进制时含数字1的个数(对应可填数字个数)
    for(int i = 0; i < (1 << 9); ++i)
        for(int j = i; j; j >>= 1)
            if(j & 1)
                cnt[i]++;
    for(int i = 0; i <= 9; ++i)//打表2的整数幂
        num[1 << i] = i;
    while(scanf("%s", a))
    {
        if(a[0] == 'e')
            break;
        init();
        DFS(tot);
        for(int i = 1; i <= 9; ++i)
            for(int j = 1; j <= 9; ++j)
                cout << b[i][j];
        cout << '\n';
    }
    return 0;
}

由于DFS不是本体正解,900ms卡过,而且是交C++过,交G++就超时

对于3076:剪枝情况多而易漏

DFS:

①遍历所有格子:处理空白格子

(1)未填空格出错:虽是空白格子,已无可填数字——return 0;

(2)唯一数字可填:某个空白格子(受所在行列宫的影响)只能填一个数字——填上

②遍历所有行:每行遍历所有数字[1, 16]

(1)已填数字出错:某一数字在一行上出现了不止一次——return 0;

(2)唯一空格可填:这一行上好多空格,某个数字只能填那一个特定的

③遍历所有列(同②)

④遍历所有宫(同②)

⑤从情况少的地方入手(这里的数据备份特重要)

Code

#include <ctime>
#include <cstdio>
#include <vector>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int INF = 0x3f3f3f3f;
#define ll long long

char b[20][20];

int tot;///空的个数

int row[20];///第i行可填的数字(二进制状态压缩为一个十进制的int)
int b_row[20];///第i行剩余的空位
int col[20];
int b_col[20];
int room[20];
int b_room[20];

int cnt[65540];
int num[65540];

/*   1  2  3  4
     5  6  7  8
     9 10 11 12
    13 14 15 16  */
int get_room(int x, int y)
{
    return (x - 1) / 4 * 4 + (y - 1) / 4 + 1;
}

int get_b_room(int x, int y)
{
    int xx = x % 4, yy = y % 4;
    if(xx == 0)
        xx = 4;
    if(yy == 0)
        yy = 4;
    return 4 * (xx - 1) + yy;
}

int get_one(int tem)
{
    int ans = 0;
    while(tem)
    {
        if(tem & 1)
            ans++;
        tem >>= 1;
    }
    return ans;
}

///某个数字的存在:
///①引起该行、该列、该九宫格不可再填该数字
///②该行对应列、该列对应行、该九宫格对应格成为已填格
void flip(int x, int y, int z)
{
    ///x行、y列、该九宫格:不能再填第z位代表的字符了 z∈[0, 15]
    row[x] ^= (1 << z);
    col[y] ^= (1 << z);
    room[get_room(x, y)] ^= (1 << z);

//    cout << "Check flip : " << x << ' ' << y << ' ' << z << '\n';
//    cout << get_room(x, y) << ' ' << get_b_room(x, y) << '\n' << '\n';
    b_row[x] ^= (1 << (y - 1));///x行的第y列已填好数字
    b_col[y] ^= (1 << (x - 1));///y列的第x行已填好数字
    b_room[get_room(x,  y)] ^= (1 << (get_b_room(x, y) - 1));///该九宫格的对应格已填好数字
    ///将[1, 16]转为[0, 15]
}

//void rev(int i, int ID, int & x, int & y)
//{
//    int rx = (ID - 1) / 4 + 1;
//    int ry = ID - 4 * (rx - 1);
//    int xx = (i - 1) / 4 + 1;
//    int yy = i - 4 * (xx - 1);
//    x = 4 * (xx - 1) + rx;
//    y = 4 * (yy - 1) + ry;
//}

void init()
{
    tot = 256;
    ///这16位是从0位开始用的
    ///(1 << 16) - 1 == 2 ^ 16 - 1 == 1111 1111 1111 1111 (二进制)
    ///默认16个数字皆可填(1可填0不可填),16个空位均未填(1待填,0已填)
    for(int i = 1; i <= 16; ++i)
        b_row[i] = b_col[i] = b_room[i] = row[i] = col[i] = room[i] = (1 << 16) - 1;
    for(int i = 1; i <= 16; ++i)
        for(int j = 1; j <= 16; ++j)
            if(b[i][j] != '-')
            {
                tot--;
                flip(i, j, b[i][j] - 'A');///    [0, 15] 代表 [A, P]
            }
}


bool DFS(int tot)
{
    if(tot == 0)
        return 1;

//    print();
    //遍历所有格子:处理空白格
    //出现矛盾:及时止损
    //唯一可选:及时填上
    for(int i = 1; i <= 16; ++i)
        for(int j = 1; j <= 16; ++j)
            if(b[i][j] == '-')
            {
                int val = row[i] & col[j] & room[get_room(i, j)];
                if(!val)//空白格没有可填数字
                    return 0;
                if(get_one(val) == 1)//空白格有唯一可填数字
                {
                    int tem = num[val & -val];//[0, 15]
                    b[i][j] = tem + 'A';
                    flip(i, j, tem);
                    tot--;
                }
            }

    //遍历所有行:对于每行遍历所有数字
    for(int i = 1; i <= 16; ++i)
        for(int k = 1; k <= 16; ++k)
        {
            int k_num = 0, choice = 0, tem_y;
            for(int j = 1; j <= 16; ++j)
            {
                int val = row[i] & col[j] & room[get_room(i, j)];
                if(b[i][j] == (k - 1) + 'A')
                    k_num++;
                if(k_num == 2)//某行出现两次k
                    return 0;
                if(b[i][j] == '-' && val & (1 << (k - 1)))//能填k的空白格
                    choice++, tem_y = j;
            }
            if(k_num == 0 && choice == 0)//k在这行未出现过,但没有能填k的空白格
                return 0;
            if(k_num == 0 && choice == 1)//数字 对 不唯一空白格 的 唯一可选(多个空白格,只能填在那个上)
            {
                b[i][tem_y] = (k - 1) + 'A';
                flip(i, tem_y, k - 1);
                tot--;
            }
        }

    for(int j = 1; j <= 16; ++j)
        for(int k = 1; k <= 16; ++k)
        {
            int k_num = 0, choice = 0, tem_x = 0;
            for(int i = 1; i <= 16; ++i)
            {
                int val = row[i] & col[j] & room[get_room(i, j)];
                if(b[i][j] == (k - 1) + 'A')
                    k_num++;
                if(k_num == 2)
                    return 0;
                if(b[i][j] == '-' && val & (1 << (k - 1)))
                    choice++, tem_x = i;
            }
            if(k_num == 0 && choice == 0)
                return 0;
            if(k_num == 0 && choice == 1)
            {
                b[tem_x][j] = (k - 1) + 'A';
                flip(tem_x, j, k - 1);
                tot--;
            }
        }

    for(int i = 1; i <= 16; ++i)
    {
        int x = (i + 3) / 4, y = i - (x - 1) * 4;
        for(int k = 1; k <= 16; ++k)
        {
            int k_num = 0, choice = 0, tem_x, tem_y;
            for(int ii = (x - 1) * 4 + 1; ii <= 4 * x; ++ii)
                for(int jj = (y - 1) * 4 + 1; jj <= 4 * y; ++jj)
                {
                    int val = row[ii] & col[jj] & room[get_room(ii, jj)];
                    if(b[ii][jj] == (k - 1) + 'A')
                        k_num++;
                    if(k_num == 2)
                        return 0;
                    if(b[ii][jj] == '-' && val & (1 << (k - 1)))
                        choice++, tem_x = ii, tem_y = jj;
                }
            if(k_num == 0 && choice == 0)
                return 0;
            if(k_num == 0 && choice == 1)
            {
                b[tem_x][tem_y] = (k - 1) + 'A';
                flip(tem_x, tem_y, (k - 1));
                tot--;
            }
        }
    }

    if(tot == 0)
        return 1;

    ///(优先处理)优先选择候选数字少的格子填数字更高效
    int mmin = INF, x, y;
    for(int i = 1; i <= 16; ++i)
        for(int j = 1 ; j <= 16; ++j)
        {
            if(b[i][j] != '-')
                continue;

            int val = row[i] & col[j] & room[get_room(i, j)];
            if(val == 0)///当前格子可填数字的个数为0
                return 0;

            if(cnt[val] < mmin)
            {
                x = i, y = j;///这个格子可填数字的个数最少
                mmin = cnt[val];///可填数字的个数
            }
        }

    int t_b[20][20], t_row[20], t_col[20], t_room[20];
    memcpy(t_b, b, sizeof(b));
    memcpy(t_row, row, sizeof(row));
    memcpy(t_col, col, sizeof(col));
    memcpy(t_room, room, sizeof(room));
    int t_tot = tot;

    int val = row[x] & col[y] & room[get_room(x, y)];
    for(; val; val -= (val & -val))///之前那种方法不太行了....
    {
        int tem = num[val & -val];
        b[x][y] = tem + 'A';
        flip(x, y, tem);
        if(DFS(tot - 1))
            return 1;
        else///原来这儿有猫腻儿
        {
            memcpy(b, t_b, sizeof(t_b));
            memcpy(row, t_row, sizeof(t_row));
            memcpy(col, t_col, sizeof(t_col));
            memcpy(room, t_room, sizeof(t_room));
            tot = t_tot;
        }
    }
    return 0;
}

/// (i & -i) == 2 ^ p   (p为i的最低位1所在位)

int main()
{
    ///十进制数字i转为二进制时含数字1的个数(对应可填数字个数)
    for(int i = 0; i < (1 << 16); ++i)
        for(int j = i; j; j >>= 1)
            if(j & 1)
                cnt[i]++;


    for(int i = 0; i <= 16; ++i)///打表2的整数幂
        num[1 << i] = i;

    while(~scanf("%s", b[1] + 1))
    {
        for(int i = 2; i <= 16; ++i)
            scanf("%s", b[i] + 1);

        init();

        DFS(tot);
        for(int i = 1; i <= 16; ++i)
        {
            for(int j = 1; j <= 16; ++j)
                cout << b[i][j];
            cout << '\n';
        }
        cout << '\n';
    }
    return 0;
}

Code(Other ‘s)

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
using namespace std;
#define LL long long
#define mem(a, b) memset(a, b, sizeof(a))
//head

const int N = 17;
int mp[N][N];///字符[A,P]转数字[1,16]
int st[N][N];///禁用标记

int sum = 0;///已填
char s[N][N+5];///原图

void add(int x, int y, int t)
{
    mp[x][y] = t;
    sum++;
    for (int i = 1; i <= 16; i++)
    {
        ///t - 1 : 依然是从0开始
        /// 或 | 1 强,多次| 本1还是1:表示该位置不可放t了
        st[i][y] |= 1<<t-1;///同列不可放t
        st[x][i] |= 1<<t-1;///同行不可放t
    }
    int xx = (x+3)/4, yy = (y+3)/4;
    for (int i = (xx-1)*4 + 1; i <= xx*4; i++)
        for (int j = (yy-1)*4 + 1; j <= yy*4; j++)
            st[i][j] |= 1<<t-1;///同16宫格不可放t
}

void print()
{
    for (int i = 1; i <= 16; i++)
    {
        for (int j = 1; j <= 16; j++)
            putchar(mp[i][j]-1+'A');
        puts("");
    }
    puts("");
}

///两种剪枝:
///①格子有唯一可填的数字

///②数字有唯一可填的格子
///②有时候一个空白格看起来有好多数字可填(其所在行列宫并不满),但某个数字(比如1)以外的数字(2~16)分别出现在了他所在的行列宫,那这一个格只能填该数字


bool dfs()
{
    if(sum == 256)
    {
        print();
        return true;
    }

    cout << "Check tot : " << 256 - sum << "---------------" << '\n';
    ///遍历每个格子:检测所有的"未填空格"
    ///有矛盾:及时返回
    ///只能填一个数字:及时填上
    print();
    for (int i = 1; i <= 16; i++)
        for (int j = 1; j <= 16; j++)
            if(!mp[i][j])
            {
                int cnt = 0, t = 0;
                for (int k = 1; k <= 16; k++)
                    if((st[i][j] & (1<<k-1)) == 0)///与& 0强,可行方案: k -> st[i][j]
                    {
                        cnt++;
                        t = k;
                        if(cnt == 2)
                            break;
                    }
                ///①未填空格出错:虽是空格,无可填数字
                if(!cnt)///当前格子虽然是空的,但是不能填数字了!(很明显没有兼顾行、列、16格)例如:这一格所在行其他数字都填了只剩4,但这一格所在16宫格有4了
                    return false;
                if(cnt == 1)///唯一可填(是由st[i][j]决定的,而st数组本身就是综合了行列宫,所以此处填上此数字是"当前方案性"正确的)
                {
                    add(i, j, t);
                    cout << "ADD 1 : ------------------\n";
                    cout << "add : " << i << ' ' << j << ' ' << t << '\n';
//                    print();
                }
                ///真就填上了???!!!这层DFS尝试方案有误怎么办?全局的mp数组不能恢复到原样啊!!
            }

    ///遍历每一行(这一行有好多空白格,但某个数字只有一个可填格子)
    for (int i = 1; i <= 16; i++)
        for (int k = 1; k <= 16; k++)///遍历16个数字
        {
            int cnt1 = 0, cnt2 = 0, y;
            for (int j = 1; j <= 16; j++)///某一行上对应的16个格子
            {
                if(mp[i][j] == k)
                    cnt1++;
                ///②已填数字出错:某行出现同一个数字两次(没有兼顾行列宫)例如:某格子根据所在列性质填了数字4,同行的一个格子根据他的所在列性质也填了数字4
                if(cnt1 == 2)
                    return false;
                if(!mp[i][j] && (st[i][j] & (1<<k-1)) == 0)///找到了空白格的可填数字k
                    cnt2++, y = j;
            }
            if(!cnt1 && !cnt2)///数字k在这一行没有出现过 同时 (没有空白格 或 空白格无可填数字)
                return false;
            if(!cnt1 && cnt2 == 1)///数字k在这一行没有出现过 同时 (此行唯一空白格mp[i][y]有唯一可填数字k)
            {
                add(i, y, k);///唯一可填(只考虑本行决定的,也"是当前方案性"正确)
                cout << "ADD 2 : ------------------\n";
                cout << "add : " << i << ' ' << y << ' ' << k << '\n';
//                print();
            }
        }

    ///遍历每一列
    for (int j = 1; j <= 16; j++)
        for (int k = 1; k <= 16; k++)
        {
            int cnt1 = 0, cnt2 = 0, x;
            for (int i = 1; i <= 16; i++)
            {
                if(mp[i][j] == k)
                    cnt1++;
                if(cnt1 == 2)
                    return false;
                if(!mp[i][j] && (st[i][j] & (1<<k-1)) == 0)
                    cnt2++, x = i;
            }
            if(!cnt1 && !cnt2)
                return false;
            if(!cnt1 && cnt2 == 1)
            {
                add(x, j, k);
                cout << "ADD 3 : --------------------- \n";
                cout << "add : " << x << ' ' << j << ' ' << k << '\n';
//                print();
            }
        }

    ///遍历每一个16宫格
    for (int i = 1; i <= 16; i++)
    {
        int x = (i+3)/4, y = i - (x-1)*4;
        for (int k = 1; k <= 16; k++)
        {
            int cnt1 = 0, cnt2 = 0, xx, yy;
            for (int ii = (x-1)*4+1; ii <= x*4; ii++)
                for (int jj = (y-1)*4+1; jj <= y*4; jj++)
                {
                    if(mp[ii][jj] == k)
                        cnt1++;
                    if(cnt1 == 2)
                        return false;
                    if(!mp[ii][jj] && (st[ii][jj] & (1<<k-1)) == 0)
                        cnt2++, xx = ii, yy = jj;
                }
            if(!cnt1 && !cnt2)
                return false;
            if(!cnt1 && cnt2 == 1)
            {
                add(xx, yy, k);
                cout << "ADD 4 : ------------------\n";
                cout << "add : " << xx << ' ' << yy << ' ' << k << '\n';
//                print();
            }
        }
    }
    if(sum == 256)
    {
        print();
        return true;
    }

    ///选择可填数字最少的空白格尝试填数
    ///①找到格子
    int mn = N, x, y;
    for (int i = 1; i <= 16; i++)
        for (int j = 1; j <= 16; j++)
            if(!mp[i][j])///空白格
            {
                int cnt = 0;
                for (int k = 1; k <= 16; k++)
                    if((st[i][j] & (1<<k-1)) == 0)
                    {
                        cnt++;///该空白格可填数字数目
                        if(cnt >= mn)
                            break;
                    }
                if(cnt < mn)
                {
                    mn = cnt;
                    x = i;
                    y = j;
                }
            }
    int tst[N][N], tmp[N][N];
    memcpy(tst, st, sizeof(st));
    memcpy(tmp, mp, sizeof(mp));
    int tsum = sum;

    ///②尝试填数
    for (int k = 1; k <= 16; k++)
        if((st[x][y] & (1<<k-1)) == 0)
        {
            add(x, y, k);///之前的唯一性add都是"当前方案"正确的,这里是尝试性add,可能是错的,便用到了上面的"出错剪枝"
            bool f = dfs();///唯一的递归口(上面的都执行完了才尝试性递归)
            if(!f)
            {
                ///数据还原
                cout << "---------------Change---------------\n";
                memcpy(st, tst, sizeof(tst));
                memcpy(mp, tmp, sizeof(tmp));
                sum = tsum;
            }
            else
                return true;
        }
    return false;
}

int main()
{
    while(scanf("%s", s[1]+1) != EOF)
    {
        for (int i = 2; i <= 16; i++)
            scanf("%s", s[i]+1);
        sum = 0;
        mem(mp, 0);
        mem(st, 0);
        for (int i = 1; i <= 16; i++)
            for (int j = 1; j <= 16; j++)
                if(isalpha(s[i][j]))
                    add(i, j, s[i][j] - 'A' + 1);
        dfs();
    }
    return 0;
}
/*
--A----C-----O-I
-J--A-B-P-CGF-H-
--D--F-I-E----P-
-G-EL-H----M-J--
----E----C--G---
-I--K-GA-B---E-J
D-GP--J-F----A--
-E---C-B--DP--O-
E--F-M--D--L-K-A
-C--------O-I-L-
H-P-C--F-A--B---
---G-OD---J----H
K---J----H-A-P-L
--B--P--E--K--A-
-H--B--K--FI-C--
--F---C--D--H-N-
*/
Site by Baole Zhao | Powered by Hexo | theme PreciousJoy