问题描述
在约瑟夫斯问题中,我们有 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 来独立于 k
。 O(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;
}