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的时候,总是先新建行,然后在新建行里根据约束条件去慢慢新建节点,这样就不会乱了。
数独是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; }
相关文章推荐
- ACM练级日志: POJ 1376
- ACM练级日志:POJ 2886 约瑟夫环,线段树和反素数
- ACM练级日志:POJ 3740 与Dancing Links
- ACM练级日志:POJ 2318 叉积的简单应用
- POJ 3074 DLX精确覆盖求解数独问题
- ACM练级日志: POJ 1389
- ACM练级日志: POJ 2155、1151
- ACM练级日志:可持久化线段树初级-POJ 2104
- ACM练级日志:HDU 4735(ACM 成都网络赛) 重复覆盖与DLX
- ACM练级日志:带权并查集与食物链
- POJ 3074 SUKODU [Dancing Links DLX精准覆盖问题]
- poj 3074(DLX)
- ACM练级日志:Level 3的线段树标记下传
- ACM练级日志:Treap个人用模板
- poj (3074:数独)Dancing Link算法神模版
- ACM练级日志:ACM2013 南京网络赛
- ACM练级日志:HDU 4286 STL-双端队列 以及Presentaion Error
- ACM练级日志:HDU 4274
- DLX(精确覆盖) POJ 3074 Sudoku
- POJ 3074 Sudoku(数据结构,DLX)