[洛谷P1784]-Dancing Links解数独

说在前面

指针实现有时候确实还挺不方便的…
数组实现的时候,下标既可以代表值,还可以代表位置
然而指针只能指向地址,要么多一次访问,要么单独开一个域来存= =
怎么想指针都不优啊…
然而实际测试出来还是很快的,至少没有被暴力踩hhhhh

写这个舞蹈链,从上午8点写到下午4点过,从自己YY到后面不得不参考别人的程序…
因为实现比较复杂,为了让程序变得更「通用」,篡改了原算法的一个小地方,写到后面才发现根本写不下去…于是看了别人的,才按照那个程序的思路把自己的程序修改了…

果然还是自己想的太多了吗,自己以为是更优秀的方法,实际上却并不是这样…


题目

洛谷P1784传送门

题面

给出一个没有填好的数独,输出一个填好的数独

输入输出格式

输入格式:
输入一个 99 的数字矩阵,包含0~9,如果该位置上为0,则表示该位置待填

输出格式:
输出一个 99 的数字矩阵,表示填好了的数独


解法

Dancing Links解决的是精准覆盖问题,需要把数独问题转化成精准覆盖问题

数独的约束条件:

  • 每个格子有且只有一个数字
  • 每行中,0~9各个数字出现且仅出现一次
  • 每列中,0~9各个数字出现且仅出现一次
  • 每宫中,0~9各个数字出现且仅出现一次

tm明显就是一个精准覆盖问题嘛,可以直接开gan了


下面是自带大常数的代码

#include 
#include 
#include 
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页有提到,网上应该也有不少资料)

你可能感兴趣的:(DLX,搜索)