【问题标题】:UVA - 1394 : And There Was One AlgorithmUVA - 1394:有一种算法
【发布时间】:2013-01-22 15:44:21
【问题描述】:

问题的链接是UVA - 1394 : And There Was One
简单的算法是扫描整个数组并在每次迭代中标记第 k 个元素,并在最后一次停止:这需要 O(n^2) 时间。
我已经搜索了一种替代算法并遇到了 chinese blog,它指出我使用延迟传播作为 O(N lgN) 时间的解决方案来分段树
研究了段树后,我尝试构建一个 O(N lgN) 时间的算法,但无济于事。


我的问题是:
1. 可以使用分段树来获得所需的运行时间吗?
2。如果是,请告诉我如何使用它们。
3。段树和“zkw”段树是一回事吗? - 他在博客中提到了 zkw 段树。
更新: 上述问题是约瑟夫斯问题的一个例子。

【问题讨论】:

    标签: algorithm data-structures segment-tree josephus


    【解决方案1】:
    1. 可以。

    2. 你可以在下面看到数据结构的描述,这里我只是提示如何在给定的问题中使用它。 我们所代表的人口显然是石头圈。我们从所有 N 个石子都活着开始,然后在每一步杀死我们树中的适当石子。 只需要知道我们当前处于哪个元素(我认为称其为 m 比较合适)。高级算法(我把细节留给你)如下(P是我们的数据结构):

      While P.size > 1:
        P.toggle(m) // remove the m-th stone
        m = P.kth(next stone to be killed)
      

      上面代码中的 P.size 就是所有剩余棋子的数量。 在下面的描述中,它对应于tree[1]。

      注意:数据结构中使用的符号k与问题输入中代表跳跃距离的符号不同。 p>

    3. 差不多。我以前没见过这个名字,但代码看起来和我见过的人们称之为population tree的代码一样。

      人口树是使用分段树的一种简单方法。你有 N 个元素,每个元素都有两种可能的状态:1 表示活着,0 表示死亡。树支持两种操作:

      • 切换编号为 i 的元素的状态。
      • 返回第 k-th 活元素的索引(以其索引的大小)。

      为了阐明第二个操作,假设一组活元素是 {1, 2, 4, 7}。 如果 N = 8,则对应状态数组 01101001(元素 0 已死亡,元素 1 处于活动状态,元素 3 处于活动状态,依此类推)。

      那么,如何实现呢? 像往常一样,树的叶子对应于 array。也就是说,如果第 i 个元素是活动的,则第 i 个叶子的值为 1,否则为 0。 每个内部节点都会记住其子节点的值之和。

      为了切换元素的状态,我们切换相应叶子的值,然后固定从该叶子到根的路径:

      const int size = 1 << 18; // 2^17 elements, the rest are internal nodes
      int tree[size]; // number of living elements in the subtree of a node
      
      void toggle(int i) {
        tree[i + size / 2] ^= 1; // toggle the leaf
        for (i /= 2; i > 0; i /= 2)
          tree[i] = tree[2 * i] + tree[2 * i + 1];
      }
      

      注意:标记节点的常用方法是使 root 等于 1,并递归地, 节点 n 的子节点是 2n2n+1

      要找到第 k-th 个生活元素,我们可以递归思考: 我们目前在节点 n 处,并且正在寻找其子树的第 k-th 个活元素(节点的子树是根于该节点的树)。 如果n的左孩子2nk或更多的活元素,设置n = 2n。 否则,我们显然会找到正确的孩子,即设置 n = 2n+1。 在这种情况下,我们不再寻找子树的第 k-th 个活元素。 因为我们跳过了左孩子的所有生活元素,所以我们从 k 中减去该计数。为简单起见,我们在此查看基于 1 的生活元素。

      这里的基本思想可能是递归的,但描述暗示迭代地做它也应该很简单:

      int kth(int k) {
        ++k; // because this method looks at elements 1-based
        int current_node = 1; // start at the root
        while (current_node < size / 2) {
          if (tree[2 * current_node] >= k) 
            current_node = 2 * current_node; // descend into the left child
          else {
            k -= tree[2 * current_node]; // fix k
            current_node = 2 * current_node + 1; // descend into the right child
          }
        }
      }
      

      这两个功能使细分树成为人口树

    这是一个数据结构问题,因此所描述的想法使用了数据结构。 但是,我想提一下,这个问题被称为 约瑟夫斯问题,并且有替代解决方案,因此您可能有兴趣查找它。

    【讨论】:

    • 我有点形成了这个想法(找到下一个元素在左或右孩子中然后递归)但它是多云的......并且thnx很多参考约瑟夫问题......我会如果可能的话给了+2...
    猜你喜欢
    • 1970-01-01
    • 2010-09-24
    • 1970-01-01
    • 1970-01-01
    • 2011-12-22
    • 1970-01-01
    • 2013-12-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多