您的位置:首页 > 其它

hihocoder1321 搜索五·数独

2018-01-25 20:33 260 查看

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;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: