说在前面
指针实现有时候确实还挺不方便的…
数组实现的时候,下标既可以代表值,还可以代表位置
然而指针只能指向地址,要么多一次访问,要么单独开一个域来存= =
怎么想指针都不优啊…
然而实际测试出来还是很快的,至少没有被暴力踩hhhhh
写这个舞蹈链,从上午8点写到下午4点过,从自己YY到后面不得不参考别人的程序…
因为实现比较复杂,为了让程序变得更「通用」,篡改了原算法的一个小地方,写到后面才发现根本写不下去…于是看了别人的,才按照那个程序的思路把自己的程序修改了…
果然还是自己想的太多了吗,自己以为是更优秀的方法,实际上却并不是这样…
题目
题面
给出一个没有填好的数独,输出一个填好的数独
输入输出格式
输入格式:
输入一个
9∗9
的数字矩阵,包含0~9,如果该位置上为0,则表示该位置待填
输出格式:
输出一个
9∗9
的数字矩阵,表示填好了的数独
解法
Dancing Links解决的是精准覆盖问题,需要把数独问题转化成精准覆盖问题
数独的约束条件:
- 每个格子有且只有一个数字
- 每行中,0~9各个数字出现且仅出现一次
- 每列中,0~9各个数字出现且仅出现一次
- 每宫中,0~9各个数字出现且仅出现一次
这tm明显就是一个精准覆盖问题嘛,可以直接开gan了
下面是自带大常数的代码
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std ;
int id[10][10] , Cid[10][10] , Rid[10][10] , Mid[10][10] , bel[10][10] ;
int Rcnt , Rmean[1000] ;
struct Node{
Node *lf , *rg , *up , *dn , *col ;
int Rnum , Cnum , Ccnt ;
}w[5005] , *C[350] , *R[1000] , *Head , *tw = w ;
void Insert( int r_ , int c_ ){
Node *nd = ++tw ;
C[c_]->Ccnt ++ ;
nd->Rnum = r_ , nd->Cnum = c_ , nd->col = C[c_] ;
nd->up = C[c_]->up ; nd->dn = C[c_] ;
C[c_]->up->dn = nd ; C[c_]->up = nd ;
if( !R[r_] ){
R[r_] = nd ;
R[r_]->lf = R[r_]->rg = R[r_] ;
} else {
nd->lf = R[r_]->lf , nd->rg = R[r_] ;
R[r_]->lf->rg = nd , R[r_]->lf = nd ;
}
}
void insertLine( int i , int j , int x ){
Rcnt ++ ; Rmean[Rcnt] = x ;
Insert( Rcnt , id[i][j] ) ;
Insert( Rcnt , Rid[i][x] ) ;
Insert( Rcnt , Cid[j][x] ) ;
Insert( Rcnt , Mid[ bel[i][j] ][x] ) ;
}
void init(){
int tmp = 0 ;
for( int i = 1 ; i <= 9 ; i ++ )
for( int j = 1 ; j <= 9 ; j ++ )
id[i][j] = ++tmp , Rid[i][j] = id[i][j] + 81 ,
Cid[i][j] = Rid[i][j] + 81 , Mid[i][j] = Cid[i][j] + 81 ;
for( int i = 1 ; i <= 9 ; i ++ ){
for( int j = 1 ; j <= 9 ; j ++ )
bel[i][j] = ( i - 1 ) / 3 * 3 + ( j + 2 ) / 3 ;
}
Head = ++tw ;
for( int i = 1 ; i <= 324 ; i ++ )
C[i] = ++tw , C[i]->Ccnt = 0 , C[i]->Cnum = i ;
for( int i = 1 ; i <= 324 ; i ++ ){
C[i]->up = C[i]->dn = C[i] ;
C[i]->rg = C[i+1] , C[i]->lf = C[i-1] ;
}
C[0] = R[0] = Head ;
C[1]->lf = Head , Head->rg = C[1] ;
C[324]->rg = Head , Head->lf = C[324] ;
}
void remove( Node *nd ){
nd->lf->rg = nd->rg ;
nd->rg->lf = nd->lf ;
for( Node *i = nd->dn ; i != nd ; i = i->dn ){
for( Node *j = i->rg ; j != i ; j = j->rg ){
j->col->Ccnt -- ;
j->up->dn = j->dn , j->dn->up = j->up ;
}
}
}
void resume( Node *nd ){
for( Node *i = nd->up ; i != nd ; i = i->up ){
for( Node *j = i->lf ; j != i ; j = j->lf ){
j->col->Ccnt ++ ;
j->up->dn = j , j->dn->up = j ;
}
}
nd->lf->rg = nd ;
nd->rg->lf = nd ;
}
int sta[10005] , topp , ans[10][10] ;
void print(){
sort( sta + 1 , sta + topp + 1 ) ;
for( int i = 1 , tmp = 0 ; i <= 9 ; i ++ ){
for( int j = 1 ; j <= 9 ; j ++ )
tmp ++ , printf( "%d " , Rmean[ sta[tmp] ] ) ;
puts( "" ) ;
}
}
bool solve(){
if( Head->rg == Head ){
print() ; return true ;
}
Node *now = Head->rg ;
for( Node *tmp = now->rg ; tmp != Head ; tmp = tmp->rg )
if( tmp->Ccnt < now->Ccnt ) now = tmp ;
remove( now ) ;
for( Node *i = now->dn ; i != now ; i = i->dn ){//当前选第i行
for( Node *j = i->rg ; j != i ; j = j->rg )//所有i行有的元素,其他行都不能有
remove( j->col ) ;
sta[++topp] = i->Rnum ;
if( solve() ) return true ;
topp -- ;
for( Node *j = i->lf ; j != i ; j = j->lf )
resume( j->col ) ;
}
resume( now ) ;
return false ;
}
int main(){
init() ;
for( int i = 1 , x ; i <= 9 ; i ++ ){
for( int j = 1 ; j <= 9 ; j ++ ){
scanf( "%d" , &x ) ;
if( !x ){
for( int k = 1 ; k <= 9 ; k ++ )
insertLine( i , j , k ) ;
} else insertLine( i , j , x ) ;
}
}
solve() ;
}
/*
8 0 0 0 0 0 0 0 0
0 0 3 6 0 0 0 0 0
0 7 0 0 9 0 2 0 0
0 5 0 0 0 7 0 0 0
0 0 0 0 4 5 7 0 0
0 0 0 1 0 0 0 3 0
0 0 1 0 0 0 0 6 8
0 0 8 5 0 0 0 1 0
0 9 0 0 0 0 4 0 0
*/
总结
这个所谓的「Dancing Links X」算法,实际上也还是个暴搜,而且是针对精确覆盖问题的暴搜,只是在原来的X算法的基础上,使用了多重表(二位链表)进行了优化而已。
而对于数独问题来说,可能还是暴力要优秀一些,空间少,代码短且好写,实际上搜索的情况也不多。反观DLX,如果不加优化,反而慢到爆炸,慢道样例根本跑不出来。所以嘛,谨慎使用=w=
(关于多重表,在「数据结构与算法分析—C语言描述」第42页有提到,网上应该也有不少资料)