• 浅谈舞蹈链


    舞蹈链解决精确覆盖问题
    一、问题引入:
    有n 个人, 每个人有一些想吃的菜. 只有你给这个人所有他想吃的菜,他才会吃.
    可是你只有m 种菜, 每样一份.你必需把菜卖完. 问最多能满足多少人.
    *精确覆盖问题的定义:给定一个由0-1组成的矩阵,是否能找到一个行的集合,使得集合中每一列都恰好包含一个1
    假设有5种菜,4个人 ,1表示他喜欢吃这种菜。
    (1)0 1 0 1 0
    (2)1 0 0 0 0
    (3)1 1 1 0 0
    (4)0 0 1 0 1
    可见最多满足第1 、2 、4。
    答案得出的过程。
    Step1:我们假设先满足第一人,那么第3人是不能选的(因为每种菜只有一份,3不能和1抢第二种菜)。
    Step2: 接着选择第2个人,很完美,两人都可以满足。
    Step3:接着选第4个人(第三个人在Step1就淘汰了。)也可以满足他。
    这个例子太好了,换一下。
    (1)0 0 1 0 1
    (2)1 1 1 0 1
    (3)0 0 1 1 1
    (4)0 0 1 1 1
    Step1:发现要覆盖第一列,必须满足第二个人。由于1.3.4人和2都喜欢吃第3种菜,所以都不能满足。
    所以只能满足第二个人。但是这时菜不能完全卖完,此问题无解。
    总结一下求解的过程:
    (1)选择任意一行。
    (2)删除选择这一行后不能选择的其他行。
    (3)继续选择
    (4)若最后没有可选择的行,但是没有完全覆盖,回到第(1)步,修改之前任意选择的行重复步骤。
    到这挺好理解的...
    二、
    发现这过程需要存储矩阵和回溯的过程。那么怎样做比较高效呢。
    舞蹈链是一种数据结构,缓存和回溯中效率惊人。不需要额外空间,接近于线性的时间,指针在数据之间
    跳跃着,像舞蹈一样所以称之为舞蹈链。(dancing links).
    舞蹈链用的数据结构是十字链表。
    我们先从双向链表过渡到十字链表吧。
    (1)双向链表
    A1 A2 A3,
    A1.right=A2,A2.left=A1,A3.right.right=A1.
    在很多实际运用中,把双向链的首尾相连,构成循环双向链.
    删除A2时,A1.right=A3,A3.left=A1,注意A2只是从双向链表中删除了,但是A2的left和right的信息并没有变。
    由于不需要开空间,时间也是挺快的。
    (2)接下来是双向十字链表。
    每个元素有四个指针,left,right,up,down.

    注意链首和链尾是双向连接的,一定要注意双向。
    列首有我们虚构的点,在第0行,这是为了方便搜索,以后代码呈现。

    三、用法。

    删除和恢复元素。(先了解后看代码)

    规则:

    沿列删除时删除左右, 保留上下.
    沿行删除时删除上下, 保留左右.
    恢复时依然沿之前的方向, 根据自己的信息把自己插进去

    四、

    那么开始的问题引入,我们怎么用舞蹈链来解决呢。

    ........................................................假装你思考过了的样子.........................orz

    首先每一行的行首表示每个人,列表示每个人爱吃的菜,然后求精确覆盖就可以啦。

    五、code

    当然很恶心。

    int l[maxn],r[maxn],u[maxn],d[maxn],tn,ren[maxn],cai[maxn];
    void shanchu(int x) {
        for(int p=r[x]; p!=x; p=r[p]) {
            for(int q=u[p]; q!=p; q=u[q]) {
                for(int s=r[q]; s!=q; s=r[s]) {
                    if(d[x]!=s) {
                        d[u[s]]=d[s];
                        u[d[s]]=u[s];
                    }
                }
            }
        }
    }
    void huifu(int x) {
        for(int p=l[x]; p!=x; p=l[p]) {
            for(int q=d[p]; q!=p; q=d[q]) {
                for(int s=l[q]; s!=q; s=l[s]) {
                    u[d[s]]=d[u[s]]=s;
                }
            }
        }
    }
    int main() {
        memset(cai,0,sizeof(cai));
        scanf("%d%d",&n,&m);
        ren[0]=tn=1;
        l[1]=r[1]=1;
        d[1]=u[1]=1;
        for(int i = 1; i <= n; i++) {
            ren[i]=++tn;
            l[tn]=r[tn]=tn;//左右方向建表
            d[ren[i-1]]=tn;//上下方向
            u[tn]=ren[i-1];
            d[tn]=1;//指向列首。
            u[1]=tn//列首指向列尾。
                 int totcai,caii,lastcai=tn;
            scanf("%d",&totcai);
            for(int j = 0; j < totcai; ++ j) {
                scanf("%d", &caii);
                ++tn;
                r[last = tn;
                  l[tn] = lastcai;
                  r[tn]=ren[i];
                  l[ren[i]]=tn;
                  lastcai = tn;
                if(!cai[caii]) { //cai数组指的是caii最后一次出现的位置编号。
                cai[caii]=u[tn]=d[tn]=tn;
                } else {
                    u[tn]=cai[caii];
                    d[tn]=d[cai[caii]];
                    u[d[tn]]=tn;
                    d[u[tn]]=tn;
                    cai[caii]=tn;
                }
            }
        }
    }
  • 相关阅读:
    深入研究.NET Core的本地化机制
    .Net Core中的Api版本控制
    如何在.NET Core控制台程序中使用依赖注入
    .NET Core中的数据保护组件
    深入理解.NET Core的基元: deps.json, runtimeconfig.json, dll文件
    Entitiy Framework Core中使用ChangeTracker持久化实体修改历史
    Spark(Hive) SQL中UDF的使用(Python)
    Spark(Hive) SQL数据类型使用详解(Python)
    Spark如何解决常见的Top N问题
    SparkContext自定义扩展textFiles,支持从多个目录中输入文本文件
  • 原文地址:https://www.cnblogs.com/zzyh/p/7189518.html
Copyright © 2020-2023  润新知