4

배열의 접두사 sum [1]을 반환하고, 요소를 업데이트하고, 요소를 배열에 삽입/제거 할 수있는 데이터 구조가 모두 O (log n)에 있습니까?동적 접두사 합

[1] "프리픽스 합"처음 세 요소 프리픽스 합 19 인 음이 아닌 정수 8 1 10 7의 배열을 소정의 주어진 인덱스 예

, 최대 제 한 모든 요소들의 합 (8 + 1 + 10). 첫 번째 요소를 7으로 업데이트하고 3을 두 번째 요소로 삽입하고 세 번째 요소를 제거하면 7 3 10 7이됩니다. 다시 말하지만 처음 세 요소의 접두사 합은 20이됩니다.

프리픽스 합계 및 업데이트의 경우 Fenwick tree이 있습니다. 하지만 O (log n)에서 추가/제거를 처리하는 방법을 모르겠습니다.

한편, Red-black tree과 같은 몇 가지 이진 검색 트리가 있으며 로그 트리는 업데이트/삽입/제거를 대수 시간으로 처리합니다. 하지만 주어진 순서를 유지하는 방법을 모르며 접두사 합을 O (log n)에 써야합니다.

답변

3

treap 암시 적 키를 사용하면이 작업을 모두 쿼리 당 O(log n) 시간으로 수행 할 수 있습니다. 암시 적 키의 개념은 매우 간단합니다. 노드에 키를 저장하지 않습니다. 대신 모든 노드에 대해 하위 트리의 크기를 유지하고이 정보를 사용하여 요소를 추가하거나 제거 할 때 적절한 위치를 찾습니다.

#include <iostream> 
#include <memory> 

struct Node { 
    int priority; 
    int val; 
    long long sum; 
    int size; 
    std::shared_ptr<Node> left; 
    std::shared_ptr<Node> right; 

    Node(long val): 
    priority(rand()), val(val), sum(val), size(1), left(), right() {} 
}; 

// Returns the size of a node owned by t if it is not empty and 0 otherwise. 
int getSize(std::shared_ptr<Node> t) { 
    if (!t) 
    return 0; 
    return t->size; 
} 

// Returns the sum of a node owned by t if it is not empty and 0 otherwise. 
long long getSum(std::shared_ptr<Node> t) { 
    if (!t) 
    return 0; 
    return t->sum; 
} 


// Updates a node owned by t if it is not empty. 
void update(std::shared_ptr<Node> t) { 
    if (t) { 
    t->size = 1 + getSize(t->left) + getSize(t->right); 
    t->sum = t->val + getSum(t->left) + getSum(t->right); 
    } 
} 

// Merges the nodes owned by L and R and returns the result. 
std::shared_ptr<Node> merge(std::shared_ptr<Node> L, 
    std::shared_ptr<Node> R) { 
    if (!L || !R) 
    return L ? L : R; 
    if (L->priority > R->priority) { 
    L->right = merge(L->right, R); 
    update(L); 
    return L; 
    } else { 
    R->left = merge(L, R->left); 
    update(R); 
    return R; 
    } 
} 

// Splits a subtree rooted in t by pos. 
std::pair<std::shared_ptr<Node>, std::shared_ptr<Node>> split(
    std::shared_ptr<Node> t, 
    int pos, int add) { 
    if (!t) 
    return make_pair(std::shared_ptr<Node>(), std::shared_ptr<Node>()); 
    int cur = getSize(t->left) + add; 
    std::pair<std::shared_ptr<Node>, std::shared_ptr<Node>> res; 
    if (pos <= cur) { 
    auto ret = split(t->left, pos, add); 
    t->left = ret.second; 
    res = make_pair(ret.first, t); 
    } else { 
    auto ret = split(t->right, pos, cur + 1); 
    t->right = ret.first; 
    res = make_pair(t, ret.second); 
    } 
    update(t); 
    return res; 
} 

// Returns a prefix sum of [0 ... pos] 
long long getPrefixSum(std::shared_ptr<Node>& root, int pos) { 
    auto parts = split(root, pos + 1, 0); 
    long long res = getSum(parts.first); 
    root = merge(parts.first, parts.second); 
    return res; 
} 

// Adds a new element at a position pos with a value newValue. 
// Indices are zero-based. 
void addElement(std::shared_ptr<Node>& root, int pos, int newValue) { 
    auto parts = split(root, pos, 0); 
    std::shared_ptr<Node> newNode = std::make_shared<Node>(newValue); 
    auto temp = merge(parts.first, newNode); 
    root = merge(temp, parts.second); 
} 

// Removes an element at the given position pos. 
// Indices are zero-based. 
void removeElement(std::shared_ptr<Node>& root, int pos) { 
    auto parts1 = split(root, pos, 0); 
    auto parts2 = split(parts1.second, 1, 0); 
    root = merge(parts1.first, parts2.second); 
} 

int main() { 
    std::shared_ptr<Node> root; 
    int n; 
    std::cin >> n; 
    for (int i = 0; i < n; i++) { 
    std::string s; 
    std::cin >> s; 
    if (s == "add") { 
     int pos, val; 
     std::cin >> pos >> val; 
     addElement(root, pos, val); 
    } else if (s == "remove") { 
     int pos; 
     std::cin >> pos; 
     removeElement(root, pos); 
    } else { 
     int pos; 
     std::cin >> pos; 
     std::cout << getPrefixSum(root, pos) << std::endl; 
    } 
    } 
    return 0; 
} 
+0

미안하지만 난 완전히 따르지 않는하십시오 treap 내가 "첫 번째 요소를 업데이트 할 방법 즉 무작위로 순서를 가지고 "? "적절한 위치를 찾아라"- 어떻게? –

+1

@EcirHana 트랩에 임의의 순서가 없습니다. – kraskevich

+0

"각 키에 (임의로 선택한) 숫자 우선 순위가 부여 된 데카르트 트리입니다." –

2

아이디어 : AVL 트리를 수정하는

여기 내 구현입니다. 추가 및 삭제는 색인별로 수행됩니다. 모든 노드는 O (log n)에서 모든 작업을 허용하도록 각 하위 트리의 개수와 합계를 유지합니다.

개념 증명 구현 add_nodeupdate_nodeprefix_sum와 :

class Node: 
    def __init__(self, value): 
     self.value = value 
     self.left = None 
     self.right = None 
     self.left_height = 0 
     self.right_height = 0 
     self.left_count = 1 
     self.left_sum = value 
     self.right_count = 0 
     self.right_sum = 0 

    def set_value(self, value): 
     self.value = value 
     self.left_sum = self.left.left_sum + self.left.right_sum+self.value if self.left else self.value 

    def set_left(self, node): 
     self.left = node 
     self.left_height = max(node.left_height, node.right_height)+1 if node else 0 
     self.left_count = node.left_count + node.right_count+1 if node else 1 
     self.left_sum = node.left_sum + node.right_sum+self.value if node else self.value 

    def set_right(self, node): 
     self.right = node 
     self.right_height = max(node.left_height, node.right_height)+1 if node else 0 
     self.right_count = node.left_count + node.right_count if node else 0 
     self.right_sum = node.left_sum + node.right_sum if node else 0 

    def rotate_left(self): 
     b = self.right 
     self.set_right(b.left) 
     b.set_left(self) 
     return b 

    def rotate_right(self): 
     a = self.left 
     self.set_left(a.right) 
     a.set_right(self) 
     return a 

    def factor(self): 
     return self.right_height - self.left_height 

def add_node(root, index, node): 
    if root is None: return node 

    if index < root.left_count: 
     root.set_left(add_node(root.left, index, node)) 
     if root.factor() < -1: 
      if root.left.factor() > 0: 
       root.set_left(root.left.rotate_left()) 
      return root.rotate_right() 
    else: 
     root.set_right(add_node(root.right, index-root.left_count, node)) 
     if root.factor() > 1: 
      if root.right.factor() < 0: 
       root.set_right(root.right.rotate_right()) 
      return root.rotate_left() 

    return root 

def update_node(root, index, value): 
    if root is None: return root 

    if index+1 < root.left_count: 
     root.set_left(update_node(root.left, index, value)) 
    elif index+1 > root.left_count: 
     root.set_right(update_node(root.right, index - root.left_count, value)) 
    else: 
     root.set_value(value) 

    return root 


def prefix_sum(root, index): 
    if root is None: return 0 

    if index+1 < root.left_count: 
     return prefix_sum(root.left, index) 
    else: 
     return root.left_sum + prefix_sum(root.right, index-root.left_count) 


import random 
tree = None 
tree = add_node(tree, 0, Node(10)) 
tree = add_node(tree, 1, Node(40)) 
tree = add_node(tree, 1, Node(20)) 
tree = add_node(tree, 2, Node(70)) 

tree = update_node(tree, 2, 30) 

print prefix_sum(tree, 0) 
print prefix_sum(tree, 1) 
print prefix_sum(tree, 2) 
print prefix_sum(tree, 3) 
print prefix_sum(tree, 4) 
+0

쿨, 고마워! 그러나 요소를 추가하면 어떻게됩니까? 나머지 모든 노드를 다시 인덱싱하지 않아도됩니까? 그건 O (n) .... –

+0

아니요, 요소 인덱스는 암시 적으로 (각 하위 트리의 노드 수에 의해) 유지됩니다. –