您的位置:首页 > 其它

ACM练级日志:POJ 3074 数独与DLX

2014-08-09 22:24 351 查看
各位还在使用百度空间的同学们,请原谅最近被我刷屏……

数独是DLX建模的最经典应用,它最好地示范了“行=决策,列=约束”的建模方法,配合快速的DLX深搜,使它成为了目前最快的求解数独的方法。

每行是一个决策,在数独里做的决策,就是每一个格填几,也就是说,你要从81*9=729种决策中挑出81种构成一个数独的解,于是我们就会有729行,每行都是一个决策。但是这也不是随便挑的,总共有4组约束条件你要满足:首先,81个格子里必须都填数,用1~81列来表示这一点,例如第一行代表1号格填1,那么第一列“1号格填了数”在这一行上就是1,“2号格填了数”就是0…… 然后,第二组81列代表“每一行都有每个数”,例如第81 + 1列代表“第1行有1”,+2列代表“第2行有2”…… 之后的81列代表“每一列都有每个数”,然后的81列是“每个小方块都有每个数”,这样矩阵就建好了,我们只需要在这上面跑一个精确匹配就完事大吉。

由于这个矩阵是我们自己攒的,所以要对那个模板的几个部分做点修改,我也放个代码上来吧。主要的修改有这么几点:

追加记录每个节点是哪一行的,不然在记录答案的时候只记录这个节点编号的话,根本不知道它对应的决策是什么;

由于我这个程序中,对于已经填好数的格子只新建1行,所以不能用行号来推断这个行到底是哪儿填几,所以我给每行做了个结构体用于记录这个信息,查起来很方便。

为了方便,新建行和新建节点分开了,InitLinks的时候,总是先新建行,然后在新建行里根据约束条件去慢慢新建节点,这样就不会乱了。

#include<iostream>
#include<stdio.h>
#include<string.h>
#include<math.h>
using namespace std;

const int INF=2<<25;
const int MAXNUM=6010;

int u[MAXNUM], d[MAXNUM], l[MAXNUM], r[MAXNUM];//上下左右
int s[MAXNUM], col[MAXNUM];//s[i]:第i列有几个节点,即有几个1,col[i]能告诉你i这个节点在第几列

int head;//总表头,其实就是0号节点
int p_nodes;//目前用了多少节点

///////////INSERT UNIQUE THINGS HERE
char input[90];
char output[90];
int ans[MAXNUM];
const int m=4*81;
int n;
struct itype
{
int pos;
int num;
}row_id[800];
int row[MAXNUM];

///////////////////////////////////

void del(int c)//删掉列c以及列c中A[i][j]=1的所有行
{
l[ r[c] ] = l[c];
r[ l[c] ] = r[c];

for(int i=d[c]; i!=c; i=d[i])
{
for(int j=r[i]; j!=i; j=r[j])
{
u[ d[j] ] = u[j];
d[ u[j] ] = d[j];
s[ col[j] ] --;
}
}
return;
}

void resume(int c)//恢复上面的操作
{
for(int i=u[c]; i!=c; i=u[i])
{
for(int j=l[i]; j!=i; j=l[j])
{
s[ col[j] ] ++;
d[ u[j] ]=j;
u[ d[j] ]=j;
}
}

r[ l[c] ] =c;
l[ r[c] ] = c;
return;
}

bool DFS(int depth)
{
////TEST
//cout<<depth<<endl;

if(r[head] == head)
return true;//矩阵被删干净了

int min1=INF, now;//挑一个1数最少列的先删
for(int t=r[head]; t!=head; t=r[t])
{
if(s[t] <=min1 )
{
min1=s[t];
now=t;
}
}

del(now);//删掉此列
int i, j;
for(i=d[now]; i!=now; i=d[i])
{//枚举这一列每个1由哪行来贡献,这行即为暂时性的答案,如果需记录答案,此时ans[depth]=row[i]
ans[depth]=row[i];
for(j=r[i]; j!=i; j=r[j])
{
del( col[j] );//选取了第i行,就要把本行所有的1所在列都删掉
}

bool tmp=DFS(depth+1);

for(j=l[i]; j!=i; j=l[j])
{
resume(col[j]);
}

if(tmp==true)
return true;

ans[depth]=0;
}
resume(now);
return false;
}

void init()
{
memset(u,0,sizeof(u));
memset(d,0,sizeof(d));
memset(l,0,sizeof(l));
memset(r,0,sizeof(r));
memset(s,0,sizeof(s));
memset(col,0,sizeof(col));

head=0;
p_nodes=0;

//INSERT UNIQUE THINGS HERE
memset(ans,0, sizeof(ans));
memset(input, 0, sizeof(input));
memset(output, 0, sizeof(output));
memset(row,0,sizeof(row));
n=0;
}

int insert_node(int row_first1, int j)//知道我当前的行第一个是谁,我在第j列
{
p_nodes++;
s[j] ++;
u[p_nodes] = u[j];
d[ u[j] ] = p_nodes;
u[j] = p_nodes;
d[p_nodes] = j;

col[p_nodes] = j;//和列的关系处理完毕

if(row_first1==-1)
{
l[p_nodes] = r[p_nodes] = p_nodes;
row_first1=p_nodes;
}
else
{
l[p_nodes] = l[row_first1];
r[ l[row_first1] ] = p_nodes;
r[p_nodes] = row_first1;
l[ row_first1 ]=p_nodes;//和行的关系处理完毕
}
row[p_nodes]=n;
return row_first1;

}

void insert_row(int pos, int num)//新建一行,一行里会插入若干结点
{
n++;
row_id
.pos=pos;
row_id
.num=num;

int row_first1=-1;
row_first1= insert_node(row_first1, pos);

int rownum=(pos-1)/9+1;
row_first1= insert_node(row_first1, 81+9*(rownum-1) + num );

int colnum=(pos-1)%9+1;
row_first1= insert_node(row_first1, 2*81 + 9*(colnum-1) + num);

int blocknum=(pos-1)/9/3*3 + ((pos-1)%9)/3 +1;
row_first1= insert_node(row_first1, 3*81 + 9*(blocknum-1) + num);
}

void InitLinks()//注意:该链表两个方向都是循环的,最上面的上面指最下面
{
int i;
r[head]=1;
l[head]=m;

for(i=1;i<=m;i++)//制作列的表头,使用结点1~m
{
l[i]=i-1;
r[i]=i+1;
if(i==m)
r[i]=head;
u[i]=i;
d[i]=i;
s[i]=0;
col[i]=i;
}

p_nodes=m;
for(i=0; i<=80; i++)
{
if(input[i]=='.')
{
for(int t=1;t<=9;t++)
{
insert_row(i+1, t);
}
}
else
{
insert_row(i+1, input[i]-'0');
}
}
}

int main()
{
while(true)
{
init();
cin>>input;
if(input[0]=='e')
break;
InitLinks();
DFS(1);
for(int i=1;i<=81;i++)
{
int code=row_id[ ans[i] ].pos;
int fill=row_id[ ans[i] ].num;
output[code-1]='0'+fill;
}
cout<<output<<endl;
}
return 0;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: