• [学习笔记] 舞蹈链(DLX)入门


    “在一个全集(X)中若干子集的集合为(S),精确覆盖((oldsymbol{Exact~Cover}))是指,(S)的子集(S*),满足(X)中的每一个元素在(S*)中恰好出现一次。在计算机科学中,精确覆盖问题指找出这样的一种覆盖,或证明其不存在。”

    (0x01) 精准覆盖问题

    ……其实是一种决策问题,给定(n)行长度为(m)(0,1)序列,要求选出一些行,使得每一列有且仅有一个(1),这就是精准覆盖问题。

    诚然,我搜索贼菜,所以暂且不考虑爆搜,引进一种叫做“X算法”的东西,其本质上是每次选取一行,之后删掉所有与这行冲突的行,同时删掉与这行冲突的列,成为一个更小的矩阵,迭代下去。如果什么时候删没了,就说明是一种可行解;否则恢复原来的状态。

    我们思考这种简洁做法的流程,发现朴素的删除与恢复无非就是将矩阵的这一个元素由(0)(1)赋值成(-1),记录一下状态回溯的时候再赋值回去,整个过程十分地漫长且繁复。而所谓所谓的”舞蹈链算法( m{DLX~(Dancing-Links ~X ~Algorithm)})“算法则是专门用来加速这一过程。

    在本人看来,( m{DLX})更像是一种包装好的数据结构,一种加速措施,能更好的让爆搜达到其理论复杂度(所以本质上还是爆搜XD)……不过说实话“像翩翩起舞的舞者”我倒是看不出来…我觉得更像是一对牛仔裤上拉链,拉来拉去的那种感觉……

    诶,什么时候我的Preface开始这么意识流了啊

    (0x02) ( ext{Dancing-Links})

    其实算法的本质就是链表,这玩意儿插入删除都是(Theta(1))的。我们考虑建立一个十字循环链表,即每个元素在链表里是四联通的,并且左右成环、上下成环,目的是方便知道某些操作该什么时候停止。本质上来讲,一个求解矩阵(此处代指上文提到的(n)(0,1)序列)初始的( ext{Dancing-Links}) 共有( ext{1+m+Count('1')}) 个元素,其中(Count('1'))指矩阵中(1)的个数。

    ( ext{m+1})个元素,大概就是列标元素((m)个)左右连成一片,最左边的(0)号元素用来判断是否( ext{worked-out})整个矩阵,和所有列标元素串成一条左右连通的链表。然后剩下的的元素就是真实存在的元素…该怎么连怎么连那种感觉…

    那么每个元素记录(6)个值,上下左右和行标列标。

    struct Node{
    	int l, r, u, d, co, ro ;
    }B[MAX << 1] ; 
    

    初始化

    inline void Init(){
    	cin >> N >> M ; 
    	for (int i = 0 ; i <= M ; ++ i) B[i].l = i - 1, B[i].r = i + 1, B[i].u = B[i].d = i ;
    	B[M].r = 0, B[0].l = M, cnt = M, memset(Ro, -1, sizeof(Ro))  ;
    }
    

    其中(R_o[])数组记录每一行的第一个元素(第一个加进来的元素

    然后Insert函数用于插入……毕竟是链表嘛,就要有个链表的样子

    inline void Insert(int R, int C){
    	Cs[C] ++, B[++ cnt].ro = R, B[cnt].co = C ;
    	B[cnt].u = C, B[cnt].d = B[C].d, B[C].d = B[B[C].d].u = cnt ; 
    	if (Ro[R] < 0) Ro[R] = B[cnt].l = B[cnt].r = cnt ; 
    	else B[cnt].l = B[Ro[R]].l, B[cnt].r = Ro[R], B[Ro[R]].l = B[B[Ro[R]].l].r = cnt ;
    }
    

    然后(C_s[])数组用来记录每一列的元素个数,用来剪枝。

    然后就是删除和恢复,都是以列为参数的函数,也都是很平凡的操作。

    inline void Del(int C){
    	B[B[C].l].r = B[C].r, B[B[C].r].l = B[C].l ; 
    	for (int i = B[C].d ; i != C ; i = B[i].d)  
    		for (int j = B[i].r ; j != i ; j = B[j].r)
    			B[B[j].d].u = B[j].u, B[B[j].u].d = B[j].d, Cs[B[j].co] -- ;
    }
    inline void Back(int C){
    	for (int i = B[C].u ; i != C ; i = B[i].u)  
    		for (int j = B[i].l ; j != i ; j = B[j].l)
    			B[B[j].d].u = j, B[B[j].u].d = j, Cs[B[j].co] ++ ;
    	B[B[C].l].r = C, B[B[C].r].l = C ;  
    } 
    

    然后是主函数

    bool dance(int step){
    	if (!B[0].r){ return (bool)(ans = step) ; } int now_c = B[0].r ; 
    	for (int i = B[0].r ; i ; i = B[i].r) 
    		now_c = Cs[i] < Cs[now_c] ? i : now_c ; 
    	Del(now_c) ; 
    	for (int i = B[now_c].d ; i != now_c ; i = B[i].d) {
    		Ans[step] = B[i].ro ; for(int j = B[i].r ; j != i ; j = B[j].r) Del(B[j].co) ;
    		if (dance(step + 1)) return 1 ;  for(int j = B[i].l ; j != i ; j = B[j].l) Back(B[j].co) ;
     	}
     	Back(now_c) ; return 0 ; 
    }
    

    有个小剪枝,就是刚才说的(C_s[])。如果每次从含有最少(1)的那一列开始删,似乎可以快好几倍。

    我发现整理算法的文章写起来真是难受啊,还是意识流比较管用。

    最后是全部的程序((Luogu4929)):

    #include <cstdio>
    #include <cstring>
    #include <iostream>
    
    #define I_used_to_rule_the_world___Seas_would_rise_when_I_gave_the_word___Now_in_the_morning_I_sleep_alone___Sweep_the_streets_I_used_to_own Init
    #define I_used_to_roll_the_dice___Feel_the_fear_in_my_enemy_s_eyes___Listen_as_the_crowd_would_sing_Now_the_old_king_is_dead__Long_live_the_king Done
    #define One_minute_I_held_the_key__Next_the_walls_were_closed_on_me__And_I_discovered_that_my_castles_stand__Upon_pillars_of_salt_pillars_of_sand work 
    
    #define MAXN 520
    #define MAX 30010
    
    using namespace std ;
    struct Node{
        int l, r, u, d, co, ro ;
    }B[MAX << 1] ; int cnt, ans ; 
    int N, M, Ans[MAX], Ro[MAX], Cs[MAX] ;
    
    inline void Init(){
        cin >> N >> M, memset(Ro, -1, sizeof(Ro)) ; 
        for (int i = 0 ; i <= M ; ++ i) B[i].l = i - 1, B[i].r = i + 1, B[i].u = B[i].d = i ;
        B[M].r = 0, B[0].l = M, cnt = M  ;
    }
    inline void Insert(int R, int C){
        Cs[C] ++, B[++ cnt].ro = R, B[cnt].co = C ;
        B[cnt].u = C, B[cnt].d = B[C].d, B[C].d = B[B[C].d].u = cnt ; 
        if (Ro[R] < 0) Ro[R] = B[cnt].l = B[cnt].r = cnt ; 
        else B[cnt].l = B[Ro[R]].l, B[cnt].r = Ro[R], B[Ro[R]].l = B[B[Ro[R]].l].r = cnt ;
    }
    inline void Del(int C){
        B[B[C].l].r = B[C].r, B[B[C].r].l = B[C].l ; 
        for (int i = B[C].d ; i != C ; i = B[i].d)  
            for (int j = B[i].r ; j != i ; j = B[j].r)
                B[B[j].d].u = B[j].u, B[B[j].u].d = B[j].d, Cs[B[j].co] -- ;
    }
    inline void Back(int C){
        for (int i = B[C].u ; i != C ; i = B[i].u)  
            for (int j = B[i].l ; j != i ; j = B[j].l)
                B[B[j].d].u = j, B[B[j].u].d = j, Cs[B[j].co] ++ ;
        B[B[C].l].r = C, B[B[C].r].l = C ;  
    } 
    bool dance(int step){
        if (!B[0].r){ return (bool)(ans = step) ; } int now_c = B[0].r ; 
        for (int i = B[0].r ; i ; i = B[i].r) 
            now_c = Cs[i] < Cs[now_c] ? i : now_c ; 
        Del(now_c) ; 
        for (int i = B[now_c].d ; i != now_c ; i = B[i].d) {
            Ans[step] = B[i].ro ; for(int j = B[i].r ; j != i ; j = B[j].r) Del(B[j].co) ;
            if (dance(step + 1)) return 1 ;  for(int j = B[i].l ; j != i ; j = B[j].l) Back(B[j].co) ;
     	}
     	Back(now_c) ; return 0 ; 
    }
    void Done(){
        int i, j, k ;
        for (i = 1 ; i <= N ; ++ i)
            for (j = 1 ; j <= M ; ++ j)
                { cin >> k ; if (k) Insert(i, j) ;}
        
    }
    inline bool work(){
        if (!dance(0)) return puts("No Solution!") ; 
        for (int i = 0 ; i < ans ; ++ i) printf("%d ", Ans[i]) ;
    }
    
    int main(){ 
        I_used_to_rule_the_world___Seas_would_rise_when_I_gave_the_word___Now_in_the_morning_I_sleep_alone___Sweep_the_streets_I_used_to_own() ; 
        I_used_to_roll_the_dice___Feel_the_fear_in_my_enemy_s_eyes___Listen_as_the_crowd_would_sing_Now_the_old_king_is_dead__Long_live_the_king() ;
        One_minute_I_held_the_key__Next_the_walls_were_closed_on_me__And_I_discovered_that_my_castles_stand__Upon_pillars_of_salt_pillars_of_sand() ; 
        return 0 ; /*
                 	I hear Jerusalem bells are ringing   Roman Cavalry choirs are singing
                 	Be my mirror my sword and shield     My missionaries in a foreign field
                    For some reason I can't explain	     Once you know there was never'
                    Never an honest word			     That was when I ruled the world
                   */
    }
    
    
  • 相关阅读:
    绘制程序流程图笔记
    强软弱虚引用
    安全点和安全区域
    垃圾回收算法
    垃圾回收相关算法
    内存访问全过程
    多级页表与快表
    分页
    虚拟内存
    内存分段机制
  • 原文地址:https://www.cnblogs.com/pks-t/p/11753781.html
Copyright © 2020-2023  润新知