大 k 上的约瑟夫斯数列

问题描述

在约瑟夫斯问题中,我们有 n 个人,编号从 1 到 n。每一回合你跳过 k 人并杀死下一个。通常你打印最后一个幸存者把我对受害者的序列感兴趣。我试图根据网上找到的建议使用循环链接列表来做到这一点。当输入像 n=123456 这样大时,我的算法仍然不够快; k=1000000000。我的时间目标不到一秒。我认为时间复杂度是 O(nk),因为所有链表操作都是 O(1),对于每个人,我必须打印他们的值并可能移动 k 步。我是否应该找到一些不同的策略,我是否遗漏了一些明显的优化技术?

BUILTIN\Administrators

解决方法

您是正确的,使用简单的链表是 Private Sub Form_BeforeInsert(Cancel As Integer) Νούμερο = DMax("Νούμερο","Πίνακας1") + 1 End Sub ,如果您想加快速度,您必须使用更好的数据结构。对于这个问题,我建议您使用 skip list 的变体。

跳过列表是一种二维结构。所有的值都在底层。每个级别只有一些值。它跳过其他。如果你想移动固定数量的步数,你向前和向上走,跳过元素,直到你可以向前和向下走到正确的位置。所有操作都是对数的。在您的情况下,这将使算法 O(nk)

有了这个结构,一个节点看起来像这样:

O(n (log(n) + log(k))

当您构建它时,您的底层将如下所示:

struct Node {
    int value;
    int count;
    struct Node* parent;
    struct Node* next;
    struct Node* first_child;
};

1 <-> 2 <-> 3 <-> ... <-> n 为空,first_child 为指向上一级的指针,parent 为 1。

您的第二层将如下所示:

count

1 <-> 3 <-> 5 <-> ... <-> (n-1 or n as appropriate) 指向下一层中的相同值,您的 first_child 指向上一层,parent 为 2。

您的第三层将如下所示:

count

1 <-> 5 <-> 9 <-> 13 <-> ... <-> (something from n-3 to n) 指向下一层中的相同值,您的 first_child 指向上一层,parent 为 4。

依此类推,直到您的最顶层只包含 count 并且下一个值不在数组的末尾。将有 1 个级别。

好的,这就是设置。我们如何删除某些东西?我将只用我不知道的语言编写未经测试的代码,请原谅语法错误:

O(log(n))

此操作需要 void remove (Node* node) { if (0 == --node->count) { /* Actually delete */ if (node->prev != null) { node->prev->next = node->next; } if (node->next != null) { node->next.prev = node->prev; } if (node->parent != null && node = node->parent.first_child) { node->parent->first_child = node->next; node->parent->value = node->next->value; } } if (node->parent != null) { remove(node->parent); } if (0 == node->count) { free(node); } } 时间,因为这是有多少个级别。

接下来我们需要一个小的辅助函数来查找块中的最后一个节点。这意味着我们要向下和向右移动,直到到达该节点。

O(log(n))

现在前进一些步骤怎么样?这有点棘手。

首先让我们同意,在某个特定地点意味着“我刚刚到过这里,正在继续前进”。所以我们可以递归地将 Node* last_in_block (Node* node) { if (node->next == null) { /* Slide down the end. */ while (node->first_child != null) { node = node->first_child; } } else { while (node->first_child != null) { Node* child = node->first_child; while (child->next->parent == node) { child = child->next; } node = child; } } return node; } 定义为“寻找要跳过的节点,然后从块中减去它的计数”。但是有一些边缘情况。再次未经测试的代码,但以下内容应该可以大致了解我的意思。

move

现在有了这个数据结构,你可以弄清楚如何向前移动、移除你站在的地方、再次向前移动等等。

,

我们可以通过使用 treap with a size decoration 来独立于 kO(n log n)

样本输入、输出:

stdin:   10 5
stdout:  4 9 5 1 8 7 0 3 6 

改编自 Kimiyuki Onaka 的代码的 C++:

#include <iostream>
#include <tuple>
#include <random>
#include <memory>

using namespace std;

template <typename T>
struct treap {
    typedef T value_type;
    typedef double key_type;
    value_type v;
    key_type k;
    shared_ptr<treap> l,r;
    size_t m_size;
    treap(value_type v)
            : v(v),k(generate()),l(),r(),m_size(1) {
    }
    static shared_ptr<treap> update(shared_ptr<treap> const & t) {
        if (t) {
            t->m_size = 1 + size(t->l) + size(t->r);
        }
        return t;
    }
    static key_type generate() {
        static random_device device;
        static default_random_engine engine(device());
        static uniform_real_distribution<double> dist;
        return dist(engine);
    }
    static size_t size(shared_ptr<treap> const & t) {
        return t ? t->m_size : 0;
    }
    static shared_ptr<treap> merge(shared_ptr<treap> const & a,shared_ptr<treap> const & b) { // destructive
        if (not a) return b;
        if (not b) return a;
        if (a->k > b->k) {
            a->r = merge(a->r,b);
            return update(a);
        } else {
            b->l = merge(a,b->l);
            return update(b);
        }
    }
    static pair<shared_ptr<treap>,shared_ptr<treap> > split(shared_ptr<treap> const & t,size_t i) { // [0,i) [i,n),destructive
        if (not t) return { shared_ptr<treap>(),shared_ptr<treap>() };
        if (i <= size(t->l)) {
            shared_ptr<treap> u; tie(u,t->l) = split(t->l,i);
            return { u,update(t) };
        } else {
            shared_ptr<treap> u; tie(t->r,u) = split(t->r,i - size(t->l) - 1);
            return { update(t),u };
        }
    }
    static shared_ptr<treap> insert(shared_ptr<treap> const & t,size_t i,value_type v) { // destructive
        shared_ptr<treap> l,r; tie(l,r) = split(t,i);
        shared_ptr<treap> u = make_shared<treap>(v);
        return merge(merge(l,u),r);
    }
    static pair<shared_ptr<treap>,shared_ptr<treap> > erase(shared_ptr<treap> const & t,size_t i) { // (t \ t_i,t_t),destructive
        shared_ptr<treap> l,u,r;
        tie(l,i+1);
        tie(l,u) = split(l,i);
        return { merge(l,r),u };
    }
};

typedef treap<int> T;
int main() {
    int n; cin >> n;
    int k; cin >> k;
    shared_ptr<T> t;
    for (int i=0; i<n; i++)
        t = T::insert(t,i,i);
    
    int size = n;
    int position = 0;
    for (int i=0; i<n-1; i++){
        position = (position - 1 + k) % size;
        size = size - 1;
        shared_ptr<T> u;
        tie(t,u) = T::erase(t,position);
        cout << u->v << " ";
    }
    cout << endl;
    return 0;
}