Dancing links 模板

Dancing links 模板

知识点传送门

  • 用途:精确覆盖问题:给定一个由0-1组成的矩阵,是否能找到一个行的集合,使得集合中每一列都恰好包含一个1

代码模版一

#include<iostream> 
#include<algorithm>
#include<cstring>
#include<queue>
#include<map>
#include<string>
#include<cstdio>
#include<cmath>
#include<stack>
void fre() { freopen("A.txt", "r", stdin); freopen("Ans.txt","w",stdout); } void Fre() { freopen("A.txt", "r", stdin);}
#define ios ios::sync_with_stdio(false)
#define Pi acos(-1)
#define pb push_back
#define fi first
#define se second
#define ll long long
#define ull unsigned long long 
#define db double
#define Pir pair<int, int>
#define PIR pair<Pir, Pir>
#define m_p make_pair
#define INF 0x3f3f3f3f
#define mod (ll)(1e9 + 7)
#define for_(i, s, e) for(int i = (ll)(s); i <= (ll)(e); i ++)
#define rep_(i, e, s) for(int i = (ll)(e); i >= (ll)(s); i --)
#define sd(a) scanf("%d", &a)
#define sc(a) scanf("%c", &a)
using namespace std;

const int mxnode = 450010;
const int mxm = 1e3 + 10;
const int mxn = 510;

struct DLX
{
    int n, m, size;     //地图尺寸n行m列,size为图中给图中的 1 的编号
    int U[mxnode], D[mxnode], L[mxnode], R[mxnode], Row[mxnode], Col[mxnode];
    int H[mxn];         //每行行头的节点
    int S[mxm];         //每列有几个节点
    int ansd, ans[mxn]; //如果有答案:则选择了ansd行,具体是那几行存储的ans中

    void init(int _n, int _m)
    {
        n = _n, m = _m;
        for_(i, 0, m)
        {
            S[i] = 0;
            U[i] = D[i] = i;        //初始状态,上下节点都指向自己的编号 i
            L[i] = i - 1;
            R[i] = i + 1;
        }
        R[m] = 0, L[0] = m;         //编号为0的节点为第一行第一个节点,它指向的节点为m;m为第一行最后一个节点它指向的0(第一行第一个节点)
        for_(i, 1, n)
            H[i] = -1;              //在第二行之后,行首节点就暂时无法确定是从哪一列的产生的
        size = m;                   //编号,每节点都有一个编号, m为第一行最后一个节点的编号,那么下一个节点的编号就是 ++ size==m+1
    }

    void link(int r, int c)        //第r行,c列
    {
        Row[++ size] = r;           //记录当前节点所在的行、列
        Col[size] = c;
        S[c] ++;                    //当前列的节点数量 ++

        D[size] = c;
        U[size] = U[c];
        D[U[c]] = size;
        U[c] = size;

        if(H[r] == -1)
            H[r] = L[size] = R[size] = size;        //初始化 头节点 以及 头结点左右节点 都是自己的编号
        else
        {
            L[size] = size - 1;
            R[size] = H[r];
            R[L[size]] = size;
            L[H[r]] = size;
        }
    }

    void remove(int c)          //删除节点c,以及在第c列有元素的行中的所有元素也要都删除
    {
        R[L[c]] = R[c];
        L[R[c]] = L[c];
        for(int i = D[c]; i != c; i = D[i])
            for(int j = R[i]; j != i; j = R[j])
                D[U[j]] = D[j], U[D[j]] = U[j], S[Col[j]] --;
    }

    void resume(int c)
    {
        R[L[c]] = c;
        L[R[c]] = c;
        for(int i = D[c]; i != c; i = D[i])
            for(int j = R[i]; j != i; j = R[j])
                D[U[j]] = U[D[j]] = j, S[Col[j]] ++;
    }

    void dance(int d)       //递归的深度
    {
        if(ansd != -1 && ansd <= d) return;     //已经找到一组答案ansd,不用继续往下递归答案了
        if(R[0] == 0)       //第一行辅助节点的只有一个,表明找到了一组答案ansd
        {
            if(ansd == -1) ansd = d;
            else if(ansd > d) ansd = d;
            return;
        }
        int c = R[0];       //找到拥有最小节点的列,从而减少递归的层数
        for(int i = R[0]; i; i = R[i])
            if(S[c] > S[i])
                c = i;
        remove(c);
        for(int i = D[c]; i != c; i = D[i])
        {
            ans[d] = Row[i];            //记录选择额的某一行
            for(int j = R[i]; j != i; j = R[j])
                remove(Col[j]);
            dance(d + 1);
            for(int j = L[i]; j != i; j = L[j])
                resume(Col[j]);
        }
        resume(c);
    }
} dl;

代码

#include<iostream>  
#include<algorithm>
#include<cstring>
#include<queue>
#include<map>
#include<string>
#include<cstdio>
#include<cmath>
#include<stack>
void fre() { freopen("A.txt", "r", stdin); freopen("Ans.txt","w",stdout); } void Fre() { freopen("A.txt", "r", stdin);}
#define ios ios::sync_with_stdio(false)
#define Pi acos(-1)
#define pb push_back
#define fi first
#define se second
#define ll long long
#define ull unsigned long long 
#define db double
#define Pir pair<int, int>
#define PIR pair<Pir, Pir>
#define m_p make_pair
#define INF 0x3f3f3f3f
#define mod (ll)(1e9 + 7)
#define for_(i, s, e) for(int i = (ll)(s); i <= (ll)(e); i ++)
#define rep_(i, e, s) for(int i = (ll)(e); i >= (ll)(s); i --)
#define sd(a) scanf("%d", &a)
#define sc(a) scanf("%c", &a)
using namespace std;

struct DLX
{
    struct node
    {
        int c, left, right, up, down;       //c 存储的 改节点元素在那一列, 剩下的四个元素 分别为当前节点的 左、右、上、下 元素
    };
    vector<node> a;             
    using vec = vector<int>;
    using maz = vector<vec>;

    vec cnt;                            //cnt[i] 用来存储 第i列的有多少个节点元素

    void build(const maz & b, int m)    //maz 为存放数据的容器、m 数据的列数
    {
        a.clear();
        cnt = vec(m + 1);               //给 cnt 容器申请空间
        for_(i, 0, m)
            a.pb({ i, (i + m) % (m + 1), (i + 1) % (m  + 1), i, i });   //人为添加的 m+1 个辅助节点,其中a[0] 处的节点 为辅助节点的头结点
        for(auto & i : b)
        {
            for(auto & j : i)           //i 为一个vec类型的容器, i 表示某行的容器,j表示i容器的中元素在第j列
            {                           //注意 a.size() 它的值 表示的意思:a.size() 为我们正要的添加的元素 cur 的编号
                node cur = { j, -1, -1, a[j].up, j };       //注意对于 相同的一列的 cur中 列数j相同均为一个数字, 注意a[j].up 中存储的是什么?, 当前这一行我们还没有更行a[j].up 的值,它的值还是 上次更新时候(a[j] = a.size(), a.size 为将要添加元素的编号)的第j列时候的值, 那么它的值就是 上次在第j列添加元素的编号
                a[cur.up].down = a.size();
                a[cur.down].up = a.size();                  //对于相同的一列(设这一列 j == x,x为常数,且为行号) a[cur.down] = a.size()  ==> a[x] == a.size() ==> 这句的意思是:不断更新第j列的 第0行的辅助元素 所指向 上一个元素; 除了这个意思之外还有:a[x] 存储当前列的最后一个元素的编号, 当我们下一次 继续在x列添加元素的时候,我们就可以通过 a[x] 的值,知道上一次我们所添加元素的编号,有了这个编号就可以链接:新添加节点 和 上一次添加的节点; 

                if(j == i.front())
                    cur.left = cur.right = a.size();        //当cur为第i行,行首 元素的时候,我们让 这个行首节点的 left = right = a.size(), 这绝不是偶然, 是为了构成横向的双向链表
                else                                        //如果cur不是行首 节点
                {
                    cur.left = a.size() - 1;
                    cur.right = a.back().right;             //注意a.back().right 中存储的值是什么?,它里面的存储的 第i行横向双向链表的第一个节点 编号,令cur.right = a.back().right 就是把cur当做横向链表的最后一个,那么这个cur 的right指针就要指向第一个元素 a.back().right
                    a[cur.left].right = a.size();
                    a[cur.right].left = a.size();           //由于cur.rgiht = a.back().right 的关系所以这一行的实在,更新横向双向链表 第一个节点 指向的最后一个节点元素cur
                }
                a.pb(cur);
                cnt[j] ++;
            }
        }
    }
    void cover(int col)
    {
        //删除第col列(我们删除第col列就是简单的把第col-1列和第cor+1的辅助元素(a[col].left、a[col+1].right)的指向 cor列的指针的指向发生改变,但是 要注意第cor 的辅助节点元素的left、right仍然指向 其两边的辅助元素(a[col-1] 和 a【col+1)), 这样我们在 恢复 这步操作造成的改变就非常容易了,值得注意的是 第col列的节点的之间纵向的纵向双向链表节点的指针指向的元素也没有 改变 
        a[a[col].left].right = a[col].right;
        a[a[col].right].left = a[col].left;
        //删除在cor列中 存在的元素的 行
        for(int i = a[col].down; i != col; i = a[i].down)
            for(int j = a[i].right; j != i; j = a[j].right)
            {
                //我们在删除 a[j]节点 所在改行的 所有节点的时候,我们只改变了它们上下两个节点的指向,而在改行的节点的指向是完全没有发生改变,这样我们 在进行 恢复 操作的时候,就可以利用这个 当前行的节点关系不改变,进行恢复
                a[a[j].up].down = a[j].down;            
                a[a[j].down].up = a[j].up;
                cnt[a[j].c] --;
            }
    }

    void uncover(int col)
    {
        //恢复第col列,改变第col列两侧的辅助元素的指向到 第col列的辅助元素就行了
        a[a[col].left].right = col;
        a[a[col].right].left = col;
        //恢复在cor列存在元素的行
        for(int i = a[col].down; i != col; i = a[i].down)
            for(int j = a[i].right; j != i; j = a[j].right)
            {
                a[a[j].up].down = j;
                a[a[j].down].up = j;
                cnt[a[j].c] ++;
            }
    }

    void dance(int & res, int lev)      //res为答案的  ,lev 为递归的层数
    {
        if(lev >= res) return;          //剪枝
        int c = -1, mn_cnt = INF;            //c正要开始递归的列,为我们要从节点少的列开始递归,这样递归的层数少;mn_cnt 辅助用来找到包含最少元素的列c
        for(int i = a[0].right; i; i = a[i].right)
        {
            if(cnt[i] < mn_cnt)
            {
                mn_cnt = cnt[i];
                c = i;
            }
        }
        if(c == -1)
        {
            res = lev;
            return;
        }

        cover(c);
        //枚举第c列存在元素的行i
        for(int i = a[c].down; i != c; i = a[i].down)
        {
            //覆盖在第i行存在元素的列
            for(int j = a[i].right; j != i; j = a[j].right)
                cover(a[j].c);
            dance(res, lev + 1);
            //恢复在第i行存在元素的列
            for(int j = a[i].left; j != i; j = a[j].left)
                uncover(a[j].c);
        }
        uncover(c);     //恢复第c列
        return;
    }
};


int main()
{


    return 0;
}

猜你喜欢

转载自blog.csdn.net/qq_34261446/article/details/107188877