The_Bharadwaj's blog

By The_Bharadwaj, history, 6 hours ago, In English

/* -------------------------------------------------------- / / ( The Authentic JS CodeBuff ) ____ _ _ | _ ) |_ __ _ _ _ __ _ | |_ __ ____ _ (_) | _ \ ' \/ _| '_/ _ / \ V V / _ || | |/_||___,_|_| __,___,_|_/_/__,_|/ | |__/ / / --------------------------------------------------------- / / Youtube: https://youtube.com/@code-with-Bharadwaj / / Github : https://github.com/Manu577228 / / ----------------------------------------------------------- */

Hey Codeforces community!

Tackling range queries efficiently is a cornerstone of competitive programming.Two powerful data structures that often come to the rescue are Segment Trees and Fenwick Trees (also known as Binary Indexed Trees). This blog post will dive deep into these structures, explaining their concepts and implementations in JavaScript/Node.js with clear examples.

Why Range Queries?

Range queries involve operations on a contiguous subarray (a range) of an array. Common examples include:

  • Sum Queries: Finding the sum of elements within a given range.

  • Minimum/Maximum Queries: Finding the minimum/maximum element within a range.

  • Update Queries: Updating elements within a given range.

  1. Segment Trees

A Segment Tree is a binary tree-based data structure that efficiently handles range queries. Each node in the Segment Tree represents a range of the original array.

  • Construction:

The root node represents the entire array. Each internal node represents a subrange, with its left child representing the left half and its right child representing the right half. Leaf nodes represent individual elements of the original array.

  • Querying:

To query a range, we traverse the tree, visiting only the nodes that overlap with the query range.

  • Updating:

To update an element, we traverse down to the corresponding leaf node and update its value. The changes propagate upwards, updating the values of parent nodes.

class SegmentTree {

constructor(arr) { this.arr = arr; this.tree = new Array(4 * arr.length); // Worst-case size of the tree this.build(0, 0, arr.length — 1); }

build(index, start, end) { if (start === end) { this.tree[index] = this.arr[start]; return; }

const mid = Math.floor((start + end) / 2);
this.build(2 * index + 1, start, mid);
this.build(2 * index + 2, mid + 1, end);
this.tree[index] = this.tree[2 * index + 1] + this.tree[2 * index + 2]; // Sum example

}

query(index, start, end, left, right) { if (right < start || end < left) { return 0; // No overlap } if (left <= start && end <= right) { return this.tree[index]; // Complete overlap } const mid = Math.floor((start + end) / 2); const leftSum = this.query(2 * index + 1, start, mid, left, right); const rightSum = this.query(2 * index + 2, mid + 1, end, left, right); return leftSum + rightSum; // Combine results }

update(index, start, end, updateIndex, value) { if (start === end) { this.tree[index] = value; this.arr[updateIndex] = value; return; }

const mid = Math.floor((start + end) / 2);
if (updateIndex <= mid) {
  this.update(2 * index + 1, start, mid, updateIndex, value);
} else {
  this.update(2 * index + 2, mid + 1, end, updateIndex, value);
}
this.tree[index] = this.tree[2 * index + 1] + this.tree[2 * index + 2]; // Update sum

} }

Usage:

const arr = [1, 3, 5, 7, 9, 11];

const segmentTree = new SegmentTree(arr); console.log(segmentTree.query(0, 0, arr.length — 1, 1, 4)); // Output: 25 (3 + 5 + 7 + 9) segmentTree.update(0, 0, arr.length — 1, 2, 10); // Update arr[2] to 10 console.log(segmentTree.query(0, 0, arr.length — 1, 1, 4)); // Output: 30 (3 + 10 + 7 + 9)

  1. Fenwick Trees (Binary Indexed Trees)

Fenwick Trees are a more compact and often faster alternative to Segment Trees for certain types of range queries, specifically prefix sum queries and updates.

  • Concept:

A Fenwick Tree uses a clever indexing scheme to implicitly represent prefix sums. Each element in the tree stores information about a specific range of the original array.

  • Operations: Both querying and updating are performed using bit manipulation, making them very efficient.

class FenwickTree { constructor(arr) { this.arr = arr; this.tree = new Array(arr.length + 1).fill(0); for (let i = 0; i < arr.length; i++) { this.update(i, arr[i]); } }

update(index, value) { index++; // 1-based indexing while (index <= this.arr.length) { this.tree[index] += value; index += index & -index; // Lowest set bit } }

query(index) { index++; // 1-based indexing let sum = 0; while (index > 0) { sum += this.tree[index]; index -= index & -index; // Lowest set bit } return sum; }

rangeSum(left, right) { return this.query(right) — this.query(left — 1); } }

Usage:

const arr = [1, 3, 5, 7, 9, 11];

const fenwickTree = new FenwickTree(arr); console.log(fenwickTree.rangeSum(1, 4)); // Output: 25 (3 + 5 + 7 + 9) fenwickTree.update(2, 5); // Update arr[2] by adding 5 (making it 10) console.log(fenwickTree.rangeSum(1, 4)); // Output: 30 (3 + 10 + 7 + 9)

Comparison:

  • Space Complexity: Segment Trees can use up to 4n space, while Fenwick Trees use n+1 space.

  • Time Complexity: Both have O(log n) time complexity for queries and updates. Fenwick Trees are often slightly faster in practice due to simpler operations.

  • Use Cases:

Segment Trees are more versatile and can handle a wider range of operations (min, max, etc.).

Fenwick Trees are best suited for prefix sum queries and updates.

Conclusion:

Segment Trees and Fenwick Trees are indispensable tools for any competitive programmer. Understanding their implementations and knowing when to use them can significantly improve your problem-solving abilities.

Happy coding!

Full text and comments »

  • Vote: I like it
  • +7
  • Vote: I do not like it

By The_Bharadwaj, history, 2 months ago, In English

This blog post delves into a well-structured Node.js code template designed for performance optimization, particularly suited for competitive programming challenges.

We'll break down the code line by line, explaining its purpose and how it contributes to efficient input/output handling and potentially faster execution.

You'll also gain insights on submitting solutions in a Node.js environment.

Code Breakdown

  1. Buffer Allocation and Initialization:

let inputBuffer = Buffer.allocUnsafe(1e7); let inputIndex = 0, inputLength = 0; let outputBuffer = Buffer.allocUnsafe(1e7); let outputIndex = 0;

  • Buffer.allocUnsafe(1e7): Creates two fixed-size buffers, inputBuffer and outputBuffer, each with a capacity of 10 million bytes (10 MB). These buffers will store input and output data, respectively, for efficient memory management.

  • inputIndex and inputLength: These variables track the current position within the inputBuffer and the number of bytes read from standard input (stdin), respectively.

  • outputIndex: Tracks the current position within the outputBuffer where output data is written.

  1. Reading Input (stdin):

const fs = require('fs'); inputLength = fs.readSync(process.stdin.fd, inputBuffer, 0, inputBuffer.length, null);

  • fs.readSync: This function synchronously reads data from standard input (stdin), which typically refers to the user's console input.

  • process.stdin.fd: Represents the file descriptor (fd) for standard input.

  • inputBuffer: The buffer where the read data will be stored.

  • 0: Starting offset within the inputBuffer for writing the read data.

  • inputBuffer.length: Maximum number of bytes to read from stdin.

  • null: No additional options are specified.

  • inputLength: After the read operation, inputLength is assigned the number of bytes successfully read from stdin. This value will be used to determine the extent of valid data in the inputBuffer.

  1. readInt Function:

function readInt() { let num = 0, sign = 1; while (inputBuffer[inputIndex] < 48 || inputBuffer[inputIndex] > 57) { if (inputBuffer[inputIndex] === 45) sign = -1; inputIndex++; } while (inputIndex < inputLength && inputBuffer[inputIndex] >= 48 && inputBuffer[inputIndex] <= 57) { num = num * 10 + (inputBuffer[inputIndex++] — 48); } return num * sign; }

  • Purpose: This function reads an integer value from the inputBuffer, handling both positive and negative numbers.

  • Logic:

  • Initializes num to 0 and sign to 1 (assuming a positive number initially).

  • Skips non-numeric characters (anything less than 48 or greater than 57 in ASCII code, which corresponds to '0' to '9') at the beginning of the input, checking for a negative sign (45 in ASCII, '-') if encountered.

  • Iterates through the inputBuffer until the end of the numeric input is reached, accumulating individual digits into the num variable by multiplying the current number by 10 and adding the new digit (obtained by subtracting 48 from the corresponding ASCII code). Accounts for the negative sign if encountered earlier.

  • Returns the final integer value after sign adjustment.

  1. writeInt Function:

let isFirstInLine = true; function writeInt(value, isLast = false) { if (!isFirstInLine) { outputBuffer[outputIndex++] = 32; // Add a space before subsequent numbers } if (value < 0) { outputBuffer[outputIndex++] = 45; value = -value; } let digits = []; do { digits.push(value % 10); value = Math.floor(value / 10); } while (value > 0); for (let i = digits.length — 1; i >= 0; i--) { outputBuffer[outputIndex++] = 48 + digits[i]; // Convert digit to ASCII character } isFirstInLine = isLast; if (isLast) { outputBuffer[outputIndex++] = 10; // Add a newline character after the last number isFirstInLine = true; } }

  1. Writing Output to stdout:

const v8Flags = [ '--turbo-inline-threshold=500',
'--max-old-space-size=256',
'--no-lazy',
'--optimize-for-size',
'--unbox-small-integers',
'--predictable',
'--no-use-idle-notification',
'--single-generation',
'--compact-maps',
'--always-compact'
];

if (v8Flags.length > 0) { process.execArgv.push('--v8-options=' + v8Flags.join(' ')); }

let a = readInt(); let b = readInt(); writeInt(Math.floor(a / 10)); writeInt(a % 10, true); writeInt(Math.floor(b / 10)); writeInt(b % 10, true); fs.writeSync(process.stdout.fd, outputBuffer.slice(0, outputIndex));

  • V8 Flags: These flags are used to optimize the V8 JavaScript engine for performance. They are specific to Node.js and can significantly impact execution speed.

  • Reading Input:

  • readInt() is called twice to read two integer values, a and b, from the input.

  • Processing and Writing Output:

  • Math.floor(a / 10) and a % 10 extract the tens and units digits of a, respectively.

  • Math.floor(b / 10) and b % 10 extract the tens and units digits of b, respectively.

  • writeInt() is called four times to write the extracted digits to the outputBuffer, with appropriate spacing and newline characters.

  • Finally, fs.writeSync(process.stdout.fd, outputBuffer.slice(0, outputIndex)) writes the contents of the outputBuffer up to the outputIndex to standard output (stdout), which is typically displayed in the console.

Submitting Solutions in Node.js

To submit a Node.js solution to a platform like Codeforces, you typically need to create a script that takes input from stdin, processes it, and writes the output to stdout. Here's a general approach:

  • Create a Node.js file: Write your solution code, including the input/output functions and the main logic, in a .js file.

  • Prepare the Input: Ensure that the input format matches the problem's requirements. If the input is provided in a file, you can use fs.readFileSync to read it. For interactive problems, you might need to handle input line by line using readline.

  • Run the Script: Execute the script using the Node.js runtime: node your_script.js.

  • Redirect Input/Output: If the platform requires specific input/output methods, you might need to redirect stdin and stdout using command-line arguments or environment variables.

  • Submit the Solution: Follow the platform's guidelines to submit your .js file and any necessary configuration files.

Additional Tips for Optimization

  • Profiling: Use Node.js profiling tools to identify performance bottlenecks in your code.

  • Memory Management: Be mindful of memory usage, especially when dealing with large input/output data.

  • Algorithm and Data Structure Choice: Select efficient algorithms and data structures for your problem.

  • V8 Flags: Experiment with different V8 flags to find the optimal configuration for your specific use case.

  • Code Clarity: Write clean and readable code to facilitate debugging and optimization. By understanding the code template and applying these optimization techniques, you can create efficient Node.js solutions for competitive programming and other performance-critical applications.

Complete Template ( Buffer based Fast IO ( Node.js by Bharadwaj ) )

let inputBuffer = Buffer.allocUnsafe(1e7); let inputIndex = 0, inputLength = 0; let outputBuffer = Buffer.allocUnsafe(1e7); let outputIndex = 0;

const fs = require('fs'); inputLength = fs.readSync(process.stdin.fd, inputBuffer, 0, inputBuffer.length, null);

function readInt() { let num = 0, sign = 1; while (inputBuffer[inputIndex] < 48 || inputBuffer[inputIndex] > 57) { if (inputBuffer[inputIndex] === 45) sign = -1; inputIndex++; } while (inputIndex < inputLength && inputBuffer[inputIndex] >= 48 && inputBuffer[inputIndex] <= 57) { num = num * 10 + (inputBuffer[inputIndex++] — 48); } return num * sign; }

let isFirstInLine = true; function writeInt(value, isLast = false) { if (!isFirstInLine) { outputBuffer[outputIndex++] = 32; } if (value < 0) { outputBuffer[outputIndex++] = 45; value = -value; } let digits = []; do { digits.push(value % 10); value = Math.floor(value / 10); } while (value > 0); for (let i = digits.length — 1; i >= 0; i--) { outputBuffer[outputIndex++] = 48 + digits[i]; } isFirstInLine = isLast; if (isLast) { outputBuffer[outputIndex++] = 10; isFirstInLine = true; } }

const v8Flags = [ '--turbo-inline-threshold=500',
'--max-old-space-size=256',
'--no-lazy',
'--optimize-for-size',
'--unbox-small-integers',
'--predictable',
'--no-use-idle-notification',
'--single-generation',
'--compact-maps',
'--always-compact'
];

if (v8Flags.length > 0) { process.execArgv.push('--v8-options=' + v8Flags.join(' ')); }

let a = readInt(); let b = readInt(); writeInt(Math.floor(a / 10)); writeInt(a % 10, true); writeInt(Math.floor(b / 10)); writeInt(b % 10, true); fs.writeSync(process.stdout.fd, outputBuffer.slice(0, outputIndex));

Full text and comments »

  • Vote: I like it
  • +1
  • Vote: I do not like it