ACM练级日志:POJ 3074 数独与DLX

各位还在使用百度空间的同学们,请原谅最近被我刷屏…… 


数独是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. 追加记录每个节点是哪一行的,不然在记录答案的时候只记录这个节点编号的话,根本不知道它对应的决策是什么;

  2. 由于我这个程序中,对于已经填好数的格子只新建1行,所以不能用行号来推断这个行到底是哪儿填几,所以我给每行做了个结构体用于记录这个信息,查起来很方便。

  3.  为了方便,新建行和新建节点分开了,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[n].pos=pos;
        row_id[n].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;
    }


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值