2024-12-07 21:07:38 +01:00

28 KiB
Raw Blame History

excalidraw-plugin tags type
parsed
excalidraw
mixed

==⚠ Switch to EXCALIDRAW VIEW in the MORE OPTIONS menu of this document. ⚠== You can decompress Drawing data with the command palette: 'Decompress current Excalidraw file'. For more info check in plugin settings under 'Saving'

Code Block

The code provided is an implementation designed to solve the Volcano Research problem efficiently using an AVL tree data structure. Heres a step-by-step explanation of how the code works and how it solves the problem:

Problem Recap

Martin and Szymon want to know which species live at specific distances from a volcano based on given queries. The solution needs to determine which species are present at a given distance and then provide the k-th species name from the alphabetically sorted list of species at that distance. If there are fewer species than k at that distance, the output should be -.

High-Level Overview of the Solution

  1. Data Representation:

    • The code uses structures (Species and Event) to store the information about species and events.
    • An AVL tree (a self-balancing binary search tree) is used to keep track of species that are currently within the queried distance range efficiently.
  2. Input Parsing and Initialization:

    • The input consists of species data (name and range) and queries (distance and index).
    • The code reads the input and populates two arrays: one for species (Species array) and one for events (Event array).
  3. Event Generation:

    • The code generates three types of events for each species:
      • START event: marks the beginning of the range where the species can be found.
      • END event: marks the end of the range.
      • QUERY event: represents each query (distance and k value).
    • These events are stored in a list and sorted based on their distance and type to handle them in an ordered manner.
  4. Processing Events:

    • The code processes each event in order of distance and type (using the compareEvents function for sorting):
      • START Event: The species becomes active at this distance, so it is added to the AVL tree.
      • END Event: The species goes out of range, so it is removed from the AVL tree.
      • QUERY Event: The code checks the AVL tree to see if there are enough species within the distance range to answer the query.
  5. AVL Tree Operations:

    • Insertion and Deletion: The AVL tree is updated when species start or end at certain distances. This keeps the tree balanced, ensuring efficient lookups.
    • Finding the k-th Species: When a query is processed, the code checks the size of the tree (getSize) to see if there are at least k species available. If so, it finds the k-th species using an in-order traversal (findKth).
  6. Output Generation:

    • The code collects the results for each query and outputs the appropriate species name if found, or - if fewer than k species are present.

Detailed Explanation of the Code

  1. Data Structures:

    • Species Structure: Contains the name, start, and end distances, and a unique ID (speciesId) for each species.
    • Event Structure: Represents different events (START, END, QUERY) with associated data like distance, species ID, and query parameters.
  2. Sorting Species by Name:

    • The speciesPtrs array is used to sort species alphabetically. Each species is assigned an ID based on this sorted order, which will help in quickly finding and outputting species names in alphabetical order.
  3. Events Sorting:

    • The events are sorted first by distance and then by type (START, QUERY, END), ensuring that they are processed in the correct order for efficient tree management.
  4. AVL Tree Functions:

    • The code uses several AVL tree functions to manage the tree:
      • insert adds a species ID to the tree.
      • erase removes a species ID when the species goes out of range.
      • findKth finds the k-th smallest element in the tree, which corresponds to the species alphabetically.
      • getSize returns the current number of species in the tree.
  5. Efficient Query Handling:

    • As events are processed in sorted order, the AVL tree always contains only the species that are active at the current distance, ensuring that queries are handled efficiently without scanning the entire dataset.
  6. Final Output:

    • The answers array collects the results for each query. The code uses the species ID from the AVL tree to look up the species name using the speciesPtrs array and prints it. If there are fewer species than k at the queried distance, it outputs a -.

Why This Approach Works Efficiently

  • Event-based Approach: By treating species ranges and queries as events, the solution efficiently processes the data in a linear sweep, avoiding unnecessary iterations over all distances.
  • AVL Tree: The self-balancing nature of the AVL tree ensures that insertion, deletion, and k-th smallest element retrieval are all efficient (O(log n)), keeping the solution within time limits even for the maximum input size.
  • Memory Management: The code efficiently uses dynamic memory to handle the large constraints of up to 300,000 species and queries.

Complexity Analysis

  • Time Complexity: Sorting events takes O(m \log m), where m = 2n + q. AVL tree operations (insert, delete, and findKth) all take O(\log n), ensuring that the solution runs efficiently within the constraints.
  • Space Complexity: The memory usage is primarily dominated by storing species, events, and the AVL tree nodes, which is handled efficiently within the provided memory limit.

In summary, the code uses an event-driven approach combined with an AVL tree to maintain a dynamic set of species efficiently, allowing for quick lookups and updates to answer the queries in the required time and space constraints.

Code Block 1

An AVL tree is a self-balancing binary search tree (BST), named after its inventors Adelson-Velsky and Landis. The AVL tree maintains its height to ensure that all operations (insertion, deletion, lookup, etc.) are performed in O(\log n) time. This efficiency is achieved by maintaining a balance factor at each node and rebalancing the tree when necessary. Let's go through the details step by step, including augmentation.

1. Structure of an AVL Tree

An AVL tree is a binary search tree where each node contains:

  • Value/Key: The value stored in the node.
  • Left and Right Pointers: Pointers to the left and right child nodes.
  • Height: The height of the node (i.e., the number of edges on the longest path from the node to a leaf).
  • Balance Factor: The balance factor is the difference in height between the left and right subtrees of the node:
    
    \text{Balance Factor} = \text{Height of Left Subtree} - \text{Height of Right Subtree}
    

An AVL tree ensures that the balance factor of every node is either -1, 0, or 1. If any node's balance factor falls outside this range, the tree is rebalanced to restore this property.

2. Operations in an AVL Tree

The main operations in an AVL tree are insertion, deletion, and lookup. Lets go through each:

a. Insertion

  1. Standard BST Insertion: Insert the node like in a regular BST, placing it in its appropriate position based on the value.
  2. Update Heights: Traverse back up the tree, updating the height of each node.
  3. Check and Fix Balance: After updating heights, check the balance factor of each node. If it goes out of the range [-1, 1], perform rotations to rebalance.

b. Rotations for Rebalancing

There are four types of rotations:

  1. Right Rotation (Single):

    • Applied when the left subtree of a node becomes too tall.
    • Rotate the subtree right, making the left child the new root of the subtree.
  2. Left Rotation (Single):

    • Applied when the right subtree of a node becomes too tall.
    • Rotate the subtree left, making the right child the new root of the subtree.
  3. Left-Right Rotation (Double):

    • Applied when the left subtree's right subtree is too tall.
    • Perform a left rotation on the left child, then a right rotation on the node.
  4. Right-Left Rotation (Double):

    • Applied when the right subtree's left subtree is too tall.
    • Perform a right rotation on the right child, then a left rotation on the node.

c. Deletion

  1. Standard BST Deletion: Remove the node like in a standard BST (with handling for nodes having 0, 1, or 2 children).
  2. Update Heights: Update the heights of the nodes as you traverse back up.
  3. Check and Fix Balance: If the balance factor goes out of range, perform the appropriate rotations to restore balance.

d. Lookup/Search

Since the AVL tree is a balanced BST, lookup/search operations are similar to a regular BST and run in O(\log n) time.

3. AVL Tree Augmentation

Augmentation involves adding extra information or functionality to the AVL tree beyond just storing values and maintaining balance. This is useful for answering more complex queries efficiently. One common augmentation is adding the size of the subtree at each node.

Augmenting with Subtree Size

In the AVL tree, we augment each node with an additional field:

  • Size: The number of nodes in the subtree rooted at this node.

The size field is updated during insertion, deletion, and rotations, just like the height. The size of a node is:


\text{Size} = 1 + \text{Size of Left Subtree} + \text{Size of Right Subtree}

This augmentation allows us to:

  1. Find the k-th smallest element efficiently.
  2. Count the number of elements within a range.
  3. Rank of an element (position in the sorted order).
Finding the k-th Smallest Element

To find the k-th smallest element:

  1. Compare k with the size of the left subtree (\text{Size of Left Subtree} + 1).
    • If k equals this value, the current node is the k-th smallest.
    • If k is smaller, the k-th smallest element is in the left subtree.
    • If k is larger, adjust k to k - (\text{Size of Left Subtree} + 1) and search in the right subtree.

This approach allows finding the k-th smallest element in O(\log n) time due to the balanced nature of the tree.

4. Example of an Augmented AVL Tree in Practice

Lets walk through an example of how the augmented AVL tree can be used to find the k-th smallest element:

Example Tree (Balanced AVL)

        15 (5)
       /    \
     10 (3)  20 (1)
    /   \
  5 (1) 12 (1)
  • Numbers in parentheses represent the size of the subtree rooted at that node.
  • To find the 3rd smallest element:
    1. Check the left subtree of the root (15). The size is 3.
    2. Since k = 3, and the size of the left subtree plus one is also 3, the roots left child (10) is the answer.

Updates and Maintenance

  • Insertion: When a node is added, the tree is rebalanced if necessary, and sizes are updated accordingly as the recursive call returns.
  • Deletion: When a node is deleted, the size is also updated as you traverse back up, and rotations are applied if the tree becomes unbalanced.

5. Applications of AVL Trees and Their Augmentations

  1. Interval Trees: AVL trees can be augmented to store intervals and efficiently answer queries like finding overlapping intervals.
  2. Order Statistics: By keeping track of subtree sizes, AVL trees can efficiently find the rank or the k-th smallest/largest element.
  3. Range Queries: Augmenting nodes with additional data (like sum or minimum/maximum) can help quickly answer range queries in O(\log n) time.

6. Summary

  • AVL Tree Basics: Its a self-balancing binary search tree ensuring O(\log n) operations by maintaining balance factors and using rotations.
  • Augmentation: Adding extra information (e.g., subtree size) enhances the functionality, allowing efficient solutions to complex problems like finding k-th smallest elements or performing range queries.
  • Efficiency: The augmented AVL tree remains efficient in terms of both time and space, as each operation still runs in O(\log n) time, making it suitable for handling large data sets.

The use of AVL trees (with or without augmentation) is fundamental in many computational problems where dynamic updates and efficient queries are required, making them a versatile tool in computer science.

Code Block 2

The provided code solves the For the Greater Good problem using a combination of sorting and a max-heap strategy. The code efficiently determines the maximum number of generators that Bob can hack while alternating between AC and DC generators. Let's go through the code step by step to understand how it works.

Problem Recap

Bob needs to hack as many generators as possible, alternating between AC and DC generators. He starts with a certain amount of experience points (XP), and each generator has:

  • Type (0 for AC, 1 for DC)
  • XP Needed: The minimum XP required to hack the generator
  • XP Generated: The amount of XP Bob gains after hacking that generator.

Bob can hack a generator if his XP is sufficient for that generator. The challenge is to find the maximum number of generators Bob can hack by alternating generator types.

Overview of the Code

  1. Data Structures:

    • Generator Struct: Represents each generator with its type, XP required, and XP generated.
    • MaxHeap: A custom max-heap structure is used to always choose the generator that yields the maximum XP from the set of generators that Bob can currently hack.
  2. Algorithm Outline:

    • Separate the generators into two lists: one for AC generators and one for DC generators.
    • Sort these lists based on the XP needed in ascending order.
    • Use two max-heaps (one for AC and one for DC) to keep track of which generators can be hacked based on Bobs current XP.
    • Simulate Bob hacking generators, alternating between AC and DC types, and try both possible starting types to find the maximum number of generators Bob can hack.

Detailed Explanation of the Code

1. Generator Struct and Sorting Function

  • The Generator struct stores the generator type, XP required (xpNeeded), and XP generated (xpGenerated).
  • The compareByXpNeeded function is used to sort generators based on the XP needed in ascending order. This helps Bob efficiently find which generators are available based on his current XP.

2. Heap Implementation

  • Heap Initialization: The initHeap function initializes a max-heap for storing pointers to generators.
  • Heapify Up and Down: These functions (heapifyUp, heapifyDown) maintain the max-heap property:
    • heapifyUp: Ensures that after inserting a new element, the heap remains valid by moving the element up as needed.
    • heapifyDown: Adjusts the heap after removing the top element, ensuring the next maximum element is at the top.
  • Push and Pop: pushHeap adds a generator to the heap, and popHeap removes and returns the generator with the highest XP generated value. This allows Bob to always select the generator that maximizes his XP gain at each step.

3. Hacking Strategy (hackGenerators Function)

The hackGenerators function implements the core logic:

  1. Separating Generators by Type:

    • It creates two arrays (ac for AC generators and dc for DC generators) and populates them based on the type of each generator.
    • Both arrays are sorted by xpNeeded using qsort.
  2. Simulating the Hacking Process:

    • The function tests two scenarios: starting with an AC generator (startingType = 0) and starting with a DC generator (startingType = 1).
    • For each starting type:
      • Initialize indices (acIdx and dcIdx) to iterate through the sorted lists.
      • Maintain two max-heaps (acHeap and dcHeap) to track available generators that Bob can hack.
      • While there are generators that Bob can hack:
        • Add all AC generators Bob can currently hack (where xpNeeded <= xp) to acHeap if the current type is AC.
        • If a generator is available in the heap, hack it (pop from the heap), increase XP, and increment the hack count.
        • Switch to the other type (AC to DC or DC to AC) for the next iteration.
    • The maximum number of generators hacked in each scenario is tracked to determine the best strategy.
  3. Return the Maximum Number of Hacks:

    • The function returns the maximum value between the two scenarios (starting with AC or starting with DC).

4. Main Function

  • The main function reads the input, initializes the array of generators, and calls hackGenerators with Bob's initial XP and the list of generators.
  • It then prints the result (maximum number of generators Bob can hack) and frees the allocated memory.

Example Walkthrough

Let's walk through an example to see how the algorithm works:

Input Example

5 4
1 3 2
0 4 1
0 10 5
1 7 3
0 22 9
  • Initial XP: 4
  • Generators:
    • (Type: DC, XP Needed: 3, XP Generated: 2)
    • (Type: AC, XP Needed: 4, XP Generated: 1)
    • (Type: AC, XP Needed: 10, XP Generated: 5)
    • (Type: DC, XP Needed: 7, XP Generated: 3)
    • (Type: AC, XP Needed: 22, XP Generated: 9)

Simulation Details

  1. Separating and Sorting Generators:

    • AC generators: [(4, 1), (10, 5), (22, 9)]
    • DC generators: [(3, 2), (7, 3)]
  2. Starting with DC:

    • Bob hacks DC (3, 2), gaining 2 XP (now XP = 6).
    • Next, Bob hacks AC (4, 1), gaining 1 XP (now XP = 7).
    • Bob hacks DC (7, 3), gaining 3 XP (now XP = 10).
    • Finally, Bob hacks AC (10, 5), gaining 5 XP (now XP = 15).
    • Total hacks: 4.
  3. Starting with AC:

    • Bob hacks AC (4, 1), gaining 1 XP (now XP = 5).
    • Next, Bob hacks DC (3, 2), gaining 2 XP (now XP = 7).
    • Bob hacks AC (10, 5), gaining 5 XP (now XP = 12).
    • Finally, Bob hacks DC (7, 3), gaining 3 XP (now XP = 15).
    • Total hacks: 4.

The maximum number of hacks is 4.

Complexity Analysis

  • Time Complexity:
    • Sorting the generators takes O(n \log n).
    • Heap operations (push and pop) each take O(\log n). Since each generator is pushed and popped at most once, the overall time complexity remains O(n \log n).
  • Space Complexity:
    • The space used for the heaps is O(n), and the arrays (ac and dc) also take O(n). Thus, the space complexity is O(n).

Summary

The code efficiently determines the maximum number of generators Bob can hack using a combination of sorting and max-heap operations to select the optimal generators at each step. By testing both starting scenarios (AC first and DC first), the code guarantees the optimal solution.

Code Block 3

Heaps Overview

Heaps are tree-based structures used for efficient priority queues. They are complete binary trees with two types:

  • Min-Heap: Root is the smallest element.
  • Max-Heap: Root is the largest element.

Max-Heap Properties

  • Each nodes value is greater than or equal to its childrens.
  • The largest value is always at the root.
  • Its a complete binary tree: fully filled except possibly the last level, filled from left to right.

Max-Heap Operations

  1. Insertion:

    • Add the new element at the next available spot.
    • Heapify Up: Swap with the parent until the heap property is restored.
  2. Deletion (Remove Max):

    • Remove the root.
    • Move the last element to the root.
    • Heapify Down: Swap with the largest child until the heap property is restored.
  3. Peek/Top: Access the maximum element (root) in O(1).

Heapify Process

  • Heapify Up: Used during insertion to move the element up until its in the right spot.
  • Heapify Down: Used during deletion to move the new root down to maintain the max-heap property.

Array Representation

Heaps are often stored in arrays:

  • Left Child: 2i + 1
  • Right Child: 2i + 2
  • Parent: (i - 1) / 2

Applications

  • Priority Queues: Efficient access to max/min elements.
  • Heap Sort: Sorts in O(n \log n).
  • Graph Algorithms: E.g., Dijkstras shortest path.

Advantages and Disadvantages

Pros:

  • Fast insertion/deletion (O(\log n)).
  • Efficient array representation.

Cons:

  • No efficient ordered traversal (unlike BST).
  • Limited to max/min element access unless augmented.

Heaps provide efficient operations for scenarios where fast access to max/min elements is needed.

Excalidraw Data

Text Elements

0:00 Intro 0:40 For the greater good - Max heap 12:59 Max heaps definition 15:44 Volcano research - Intervals + AVL Tree 30:10 Volcano Research - Overview of Code 33:48 IMPORTANT: Volcano Research - AVL Tree Code ^isYmpx3J

FKUJp7bM: Practical 5#Code Block 9gZqEpBZ: Practical 5#Code Block 1 zteJnsSi: Practical 5#Code Block 2 mivngCCL: Practical 5#Code Block 3

%%

Drawing

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

%%