现在的位置: 首页 > 综合 > 正文

Dancing Links

2019年02月09日 ⁄ 综合 ⁄ 共 2469字 ⁄ 字号 评论关闭

Dancing links是一种能高效实现Knuth的X算法的技术,它可以使很多搜索问题得到极大的优化

假设x是一个双向链表中的一个节点,L[x]表示X的前驱,R[x]表示x的后继,

则R[L[x]] = R[x], L[R[x]] = L[x]这一操作可以把x从链表中移除,这是众所周知的,

当然,一个细致的程序员还会用 L[x] = R[x] = x或 L[x] = R[x] = NULL这样的操作来清除x,

以免发生内存泄露,所以只有很少的程序员意识到,若不清除x则 R[L[x]] = x, L[R[x]] = x

这个操作可以把刚才移除的x恢复到之前的链表中,而这样的恢复功能在有些时候是很有用的,

例如,在一个交互性的程序中,用户很可能想撤销他所做的一个或一系列操作,恢复到先前的状态。

另一个典型的应用是在回溯程序中。

精确覆盖问题:给出一个集合S={a,b,c,d,e,f,g}的一些子集S1={c,e,f}, S2={a,d,g},

 S3={b,c,f}, S4={a,d},S5={b,g}, S6={d,e,g},选取哪些子集可以使得A中的元素全部出现并只出现一次,

即用那些子集可以完全覆盖全集S,并且保证没有元素会被重复覆盖?

({S1={c,e,f},S4={a,d},S5={b,g}}是一组解)

我们可以用一个0-1矩阵来表示这些集合:

矩阵M中每一列对应全集中的一个元素,若M[row=i][col=j]=1则表示集合i包含j元素,

如:第一行S1的c、e、f列为1表示S1包含元素c、e、f。

因此我们可以把之前的问题描述为:给定一个由0和1组成的矩阵M,是否能找到一个行的集合,

使得集合中每一列都恰好包含一个1?

不管怎么说,这都是一个很难的问题,当每行恰包含3个1 时,这是个一个NP-完全问题,自然,

作为首选的算法就是回溯了。

解决精确覆盖问题: Knuth 的X算法,它能够找到由特定的01矩阵定义的精确覆盖问题的所有解。

http://blog.csdn.net/sunny606/article/details/7833551

详细的解说:

http://www.cnblogs.com/steady/archive/2011/03/15/1984791.html

这里有很多例题:

http://www.cppblog.com/notonlysuccess/archive/2009/07/10/89701.html

选了poj的两道题:

http://blog.csdn.net/dicpu/article/details/7407163

http://blog.sina.com.cn/s/blog_62ebd4410100g53u.html

http://scturtle.is-programmer.com/posts/27861.html

看了比较久了,还是不会,等以后有时间再搞吧。

poj3740:精确不覆盖分配

题意:

照着别人代码自己写了一下:

注意数组大小要开大点。

#include<cstdio>
#define maxn 700
#define maxm 6666
#define max 0x7fffffff
int n,m,size;
int L[maxm],R[maxm],U[maxm],D[maxm];
int H[maxn],C[maxm],S[maxn];
void init()
{
    for(int i=0;i<=m;i++)
    {
        S[i]=0;
        D[i]=U[i]=i;
        R[i]=i+1,L[i+1]=i;
    }
    R[m]=0;
    size=m+1;
}
void link(int r,int c)
{
    ++S[C[size]=c];//新建一个点
    D[size]=D[c];
    U[D[c]]=size;
    U[size]=c;
    D[c]=size;
    if(H[r]<0)
    H[r]=L[size]=R[size]=size;
    else
    {
        R[size]=R[H[r]];
        L[R[size]]=size;
        L[size]=H[r];
        R[H[r]]=size;
    }
    size++;
}
void remove(int 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])//?
    U[D[j]]=U[j],D[U[j]]=D[j],--S[C[j]];//?
}
void resume(int c)
{
    R[L[c]]=L[R[c]]=c;
    for(int i=U[c];i!=c;i=U[i])
    for(int j=L[i];j!=i;j=L[j])
    U[D[j]]=D[U[j]]=j,++S[C[j]];
}
bool dancing(int k)
{
    if(R[0]==0)
    {
        puts("Yes, I found it");
        return 1;
    }
    int tmp,c;
    for(int tmp=max,i=R[0];i;i=R[i])
    {
        if(S[i]<tmp)
        tmp=S[c=i];
    }
    remove(c);
    for(int i=D[c];i!=c;i=D[i])
    {
        for(int j=R[i];j!=i;j=R[j]) remove(C[j]);//?
        if(dancing(k+1)) return 1;
        for(int j=L[i];j!=i;j=L[j]) resume(C[j]);
    }
    resume(c);
    return 0;
}
int main()
{
    while(scanf("%d%d",&n,&m)!=EOF)
    {
        init();
        for(int i=1; i<=n; i++)
        {
            H[i]=-1;
            for(int j=1; j<=m; j++)
            {
                int a;
                scanf("%d",&a);
                if(a)
                    link(i,j);
            }
        }
        if(!dancing(0))
        puts("It is impossible");
    }
    return 0;
}

我想了很久才想清楚:

删除的程序是按行,列,行来进行的。先找S【】最小的列,然后找有哪些行与此列交叉,遍历每个行,把除此列外的交叉点,本行其他的所有交叉点所在行去掉,然后再吧所在列去掉,最后吧此列上的交叉点的行在全部去掉。

抱歉!评论已关闭.