hihocoder1321 搜索五·数独

DLX

题目传送门

DLX的一个经典应用。

首先我们需要把这个数独问题转化为一个精确覆盖问题。

根据数独的规则,我们有如下几个限制:

1.每个数字在每一行只能出现一次。限制编号1~81

2.每个数字在每一列只能出现一次。限制编号82~162

3.每个数字在每一个九宫格内只能出现一次。限制编号163~243

4.每一格只能填其中一个数字。限制编号244~364

这些限制就是列。

而数独的全部填法一共有9*81=729种。这些填法就是行。

原问题就转化为一个729*364大小的01矩阵的精确覆盖问题。
加一个优化:每次不选择h的右指针。增加一个数组cnt记录当前列的节点个数,每次选择cnt最小的那个。当删除或恢复时相应地更新cnt。

跑一边DLX就好了。

又臭又长的代码:

#include<cstdio>
#include<cstring>
#include<algorithm>
#define N 81*9
#define M 81*4
using namespace std;
struct nd{
    nd *u,*d,*l,*r; int x,y;
}c[M+5],a[N*M+5],*h=&c[0],s,*null=&s;
int t,ti,n=N,m=M,cnt[M+5],mp[10][10],ans[10][10],id[N+5][M+5];
bool f[N+5][M+5];
inline void nsrt(int x,int y,int z){//在(x,y)填入的值为k时的限制
    int i=(x-1)*9+y,j=(i-1)*9+z;
    f[j][(x-1)*9+z]=true;//行限制
    f[j][81+(y-1)*9+z]=true;//列限制
    int k=((x-1)/3*3+(y-1)/3)+1;
    f[j][162+(k-1)*9+z]=true;//九宫格限制
    f[j][243+i]=true;//每一格的限制
}
inline void build(){
    memset(f,false,sizeof(f));
    memset(cnt,0,sizeof(cnt));
    for (int i=1;i<=9;i++)
        for (int j=1;j<=9;j++)
            if (!mp[i][j])//如果是0的话什么都能填 
                for (int k=1;k<=9;k++) nsrt(i,j,k);
            else nsrt(i,j,mp[i][j]);//否则只能填这个
    h=&c[0],h->d=h->u=h->l=h->r=h,h->x=h->y=0,ti=0; nd *pre=h; 
    for (int i=1;i<=m;i++){
        nd *p=&c[i]; p->u=p,p->d=p,p->x=0,p->y=i;
        p->r=pre->r,p->l=pre,pre->r->l=p,pre->r=p,pre=p;
    }
    for (int i=1;i<=n;i++)
        for (int j=1;j<=m;j++)
            if (f[i][j]){
                a[id[i][j]=++ti].x=i,a[ti].y=j;
                a[ti].l=a[ti].r=a[ti].d=a[ti].u=&a[ti];
            }
    for (int j=1;j<=m;j++){
        nd *pre=&c[j];
        for (int i=1;i<=n;i++)
            if (f[i][j]){
                cnt[j]++; nd *p=&a[id[i][j]];
                p->d=pre->d,p->u=pre,pre->d->u=p,pre->d=p,pre=p;
            }
    }
    for (int i=1;i<=n;i++){
        nd *pre=null;
        for (int j=1;j<=m;j++)
            if (f[i][j])
                if (pre==null) pre=&a[id[i][j]];
                else{
                    nd *p=&a[id[i][j]]; p->r=pre->r;
                    p->l=pre,pre->r->l=p,pre->r=p,pre=p;
                }
    }
}
inline void rmv(int x){
    nd *p=&c[x],*p1=p->d;
    p->r->l=p->l,p->l->r=p->r;
    while (p1!=p){
        nd *p2=p1->r;
        while (p2!=p1)
            p2->d->u=p2->u,p2->u->d=p2->d,cnt[p2->y]--,p2=p2->r;
        p1=p1->d;
    }
}
inline void rsm(int x){
    nd *p=&c[x],*p1=p->d;
    p->r->l=p->l->r=p;
    while (p1!=p){
        nd *p2=p1->r;
        while (p2!=p1)
            p2->d->u=p2->u->d=p2,cnt[p2->y]++,p2=p2->r;
        p1=p1->d;
    }
}
inline nd *find(){//每次选择cnt最小的
    nd *p=h->r,*ans=h->r; int mn=0x7fffffff;
    while (p!=h){
        if (cnt[p->y]<mn) mn=cnt[p->y],ans=p;
        p=p->r;
    }
    return ans;
}
bool dfs(){
    if (h->r==h) return true;
    nd *p=find(),*p1=p->d;
    if (p1==p) return false; rmv(p->y);
    while (p1!=p){
        //答案数组
        ans[(p1->x-1)/81+1][((p1->x-1)/9)%9+1]=(p1->x-1)%9+1;
        nd *p2=p1->r;
        while (p2!=p1) rmv(p2->y),p2=p2->r;
        if (dfs()) return true; p2=p1->l;
        while (p2!=p1) rsm(p2->y),p2=p2->l;
        p1=p1->d;
    }
    return rsm(p->y),false;
}
int main(){
    scanf("%d",&t);
    while (t--){
        for (int i=1;i<=9;i++)
            for (int j=1;j<=9;j++)
                scanf("%d",&mp[i][j]);
        build(),dfs();
        for (int i=1;i<=9;i++){
            for (int j=1;j<=8;j++)
                printf("%d ",ans[i][j]);
            printf("%d\n",ans[i][9]);
        }
    }
    return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值