feat: add generic iterative segment tree class
This commit is contained in:
@@ -57,3 +57,4 @@ Thumbs.db
|
||||
|
||||
# binary
|
||||
sub_notifs
|
||||
iter_seg_tree
|
||||
@@ -0,0 +1,8 @@
|
||||
cmake_minimum_required(VERSION 4.0)
|
||||
|
||||
project(iter_seg_tree)
|
||||
set(CMAKE_CXX_STANDARD 20)
|
||||
|
||||
add_executable(iter_seg_tree src/main.cpp)
|
||||
|
||||
target_include_directories(iter_seg_tree PRIVATE ${CMAKE_SOURCE_DIR}/include)
|
||||
@@ -0,0 +1,69 @@
|
||||
#pragma once
|
||||
#include <functional>
|
||||
#include <vector>
|
||||
|
||||
#define LEFT(i) (i << 1)
|
||||
#define RIGHT(i) ((i << 1) | 1)
|
||||
#define IS_RIGHT_CHILD(i) ((i) & 1)
|
||||
|
||||
template <typename T>
|
||||
class IterSegTree {
|
||||
private:
|
||||
int len_;
|
||||
std::vector<T> seg_tree_;
|
||||
std::function<T(T, T)> combine_;
|
||||
T iota_;
|
||||
|
||||
void build(const std::vector<T>& base_array) {
|
||||
// n to 2n-1 => total n elements => leaves
|
||||
for (int i = len_; i < 2 * len_; i++) {
|
||||
seg_tree_[i] = base_array[i - len_];
|
||||
}
|
||||
// reverse as back to front is going up the tree
|
||||
for (int i = len_ - 1; i > 0; i--) {
|
||||
seg_tree_[i] = combine_(seg_tree_[LEFT(i)], seg_tree_[RIGHT(i)]);
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
IterSegTree(const std::vector<T>& base_array,
|
||||
std::function<T(T, T)> combine_func, T iota_value)
|
||||
: combine_(combine_func), iota_(iota_value) {
|
||||
len_ = base_array.size();
|
||||
seg_tree_.resize(2 * len_ + 1);
|
||||
build(base_array);
|
||||
}
|
||||
|
||||
IterSegTree(int len, T iota)
|
||||
: len_(len), iota_(iota), seg_tree_(2 * len_ + 1, iota_) {}
|
||||
|
||||
void update(int index, std::function<T(T)> transform) {
|
||||
// 1. go to leaf and update
|
||||
index += len_;
|
||||
seg_tree_[index] = transform(seg_tree_[index]);
|
||||
|
||||
while (index > 1) { // we don't use 0
|
||||
index >>= 1; // move to parent first
|
||||
seg_tree_[index] =
|
||||
combine_(seg_tree_[LEFT(index)], seg_tree_[RIGHT(index)]);
|
||||
}
|
||||
}
|
||||
|
||||
// for range [l,r)
|
||||
T query(int left, int right) {
|
||||
left += len_;
|
||||
right += len_;
|
||||
|
||||
T result = iota_;
|
||||
// NOTE: this soln assumes combine_ is commutative,
|
||||
// else you need to separarte left and right accumulators
|
||||
for (; left < right; left >>= 1, right >>= 1) {
|
||||
if (IS_RIGHT_CHILD(left))
|
||||
result = combine_(result, seg_tree_[left++]);
|
||||
if (IS_RIGHT_CHILD(right))
|
||||
result = combine_(result, seg_tree_[--right]);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,45 @@
|
||||
#pragma once
|
||||
#include <functional>
|
||||
#include <vector>
|
||||
|
||||
#define LEFT(i) (i << 1)
|
||||
#define RIGHT(i) ((i << 1) | 1)
|
||||
#define IS_RIGHT_CHILD(i) ((i) & 1)
|
||||
|
||||
// REMEMBER REMEMBER REMEMBER REMEMBER
|
||||
// Invariant: tree[p] will reflect the effect ot lazy[p]
|
||||
// the lazy is not for the node, but for the children
|
||||
|
||||
template <typename T, typename L>
|
||||
class IterLazySegTree {
|
||||
private:
|
||||
int n_, h_;
|
||||
std::vector<T> tree_;
|
||||
std::vector<L> lazy_;
|
||||
|
||||
T iota_;
|
||||
L iota_lazy_;
|
||||
|
||||
std::function<T(T, T)> combine_;
|
||||
std::function<L(L, L)> combine_lazy_;
|
||||
std::function<T(T, L, int)> apply_lazy_;
|
||||
|
||||
void apply(int index, L new_lazy, int len) {
|
||||
// invariant: tree_[index] has no lazy, so just need the
|
||||
// new val to be applied
|
||||
tree_[index] = apply_lazy_(tree_[index], new_lazy, len);
|
||||
|
||||
if (index < n_) { // not leaf
|
||||
// combine lazy for children
|
||||
lazy_[index] = combine_lazy_(lazy_[index], new_lazy);
|
||||
}
|
||||
}
|
||||
|
||||
void push(int index) {
|
||||
for (int cur_h = h_; cur_h > 0; cur_h--) {
|
||||
int par_at_level_cur_h = index >> cur_h;
|
||||
if (lazy_[par_at_level_cur_h] != iota_lazy_) {
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,445 @@
|
||||
<!-- AI GENERATED SIMULATION -->
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>Iterative SegTree (Dark Mode)</title>
|
||||
<style>
|
||||
:root {
|
||||
/* Dark Mode Palette */
|
||||
--bg-body: #0f172a; /* Slate 950 */
|
||||
--bg-panel: #1e293b; /* Slate 800 */
|
||||
--text-main: #f1f5f9; /* Slate 100 */
|
||||
--text-muted: #94a3b8; /* Slate 400 */
|
||||
|
||||
--node-bg: #334155; /* Slate 700 */
|
||||
--node-border: #475569; /* Slate 600 */
|
||||
|
||||
/* Accents (Lighter for dark mode visibility) */
|
||||
--highlight-l: #60a5fa; /* Blue 400 */
|
||||
--highlight-r: #f87171; /* Red 400 */
|
||||
--selected: #10b981; /* Emerald 500 */
|
||||
--selected-bg: #064e3b; /* Emerald 900 */
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: "Segoe UI", system-ui, sans-serif;
|
||||
background-color: var(--bg-body);
|
||||
color: var(--text-main);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 20px;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
h2 {
|
||||
margin-bottom: 10px;
|
||||
font-weight: 300;
|
||||
letter-spacing: 1px;
|
||||
}
|
||||
|
||||
/* --- Controls --- */
|
||||
.controls {
|
||||
background: var(--bg-panel);
|
||||
padding: 15px 25px;
|
||||
border-radius: 12px;
|
||||
border: 1px solid var(--node-border);
|
||||
display: flex;
|
||||
gap: 15px;
|
||||
align-items: center;
|
||||
margin-bottom: 20px;
|
||||
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.3);
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
input {
|
||||
width: 40px;
|
||||
padding: 8px;
|
||||
text-align: center;
|
||||
background: var(--bg-body);
|
||||
border: 1px solid var(--node-border);
|
||||
color: var(--text-main);
|
||||
border-radius: 6px;
|
||||
font-size: 16px;
|
||||
}
|
||||
input:focus {
|
||||
outline: 2px solid var(--highlight-l);
|
||||
border-color: transparent;
|
||||
}
|
||||
|
||||
button {
|
||||
padding: 8px 16px;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
font-weight: 600;
|
||||
transition: all 0.2s;
|
||||
color: #fff;
|
||||
}
|
||||
#btn-start {
|
||||
background-color: #2563eb;
|
||||
} /* Blue 600 */
|
||||
#btn-start:hover {
|
||||
background-color: #3b82f6;
|
||||
}
|
||||
|
||||
#btn-step {
|
||||
background-color: #d97706;
|
||||
} /* Amber 600 */
|
||||
#btn-step:hover {
|
||||
background-color: #f59e0b;
|
||||
}
|
||||
|
||||
#btn-reset {
|
||||
background-color: #475569;
|
||||
} /* Slate 600 */
|
||||
#btn-reset:hover {
|
||||
background-color: #64748b;
|
||||
}
|
||||
|
||||
button:disabled {
|
||||
opacity: 0.3;
|
||||
cursor: not-allowed;
|
||||
filter: grayscale(1);
|
||||
}
|
||||
|
||||
/* --- Info & Results --- */
|
||||
.info-panel {
|
||||
width: 100%;
|
||||
max-width: 800px;
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.explanation {
|
||||
flex: 2;
|
||||
background: var(--bg-panel);
|
||||
padding: 15px;
|
||||
border-left: 4px solid var(--highlight-l);
|
||||
border-radius: 6px;
|
||||
min-height: 60px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
line-height: 1.5;
|
||||
}
|
||||
.result-box {
|
||||
flex: 1;
|
||||
background: var(--bg-panel);
|
||||
padding: 15px;
|
||||
border-radius: 6px;
|
||||
border: 1px dashed var(--node-border);
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
align-content: flex-start;
|
||||
}
|
||||
.mini-badge {
|
||||
font-size: 11px;
|
||||
background: var(--selected-bg);
|
||||
color: var(--selected);
|
||||
border: 1px solid var(--selected);
|
||||
padding: 4px 8px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
/* --- Visualization Area --- */
|
||||
#viz-container {
|
||||
width: 800px;
|
||||
height: 400px;
|
||||
background: var(--bg-panel);
|
||||
border-radius: 12px;
|
||||
border: 1px solid var(--node-border);
|
||||
position: relative;
|
||||
box-shadow: inset 0 0 30px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
/* --- Nodes --- */
|
||||
.node {
|
||||
position: absolute;
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
background: var(--node-bg);
|
||||
border: 2px solid var(--node-border);
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
font-weight: bold;
|
||||
color: var(--text-main);
|
||||
transition: all 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275);
|
||||
cursor: default;
|
||||
transform: translate(-50%, -50%);
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
/* Index label above node */
|
||||
.node .idx {
|
||||
position: absolute;
|
||||
top: -18px;
|
||||
font-size: 10px;
|
||||
color: var(--text-muted);
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
/* L/R Pointer label below node */
|
||||
.node .ptr-label {
|
||||
position: absolute;
|
||||
bottom: -28px;
|
||||
font-size: 16px;
|
||||
font-weight: 900;
|
||||
opacity: 0;
|
||||
transition: opacity 0.2s;
|
||||
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
/* --- Active States --- */
|
||||
.node.ptr-l {
|
||||
border-color: var(--highlight-l);
|
||||
box-shadow: 0 0 15px rgba(96, 165, 250, 0.4);
|
||||
background: #1e3a8a; /* Darker Blue */
|
||||
}
|
||||
.node.ptr-l .ptr-label {
|
||||
opacity: 1;
|
||||
color: var(--highlight-l);
|
||||
}
|
||||
|
||||
.node.ptr-r {
|
||||
border-color: var(--highlight-r);
|
||||
box-shadow: 0 0 15px rgba(248, 113, 113, 0.4);
|
||||
background: #450a0a; /* Darker Red */
|
||||
}
|
||||
.node.ptr-r .ptr-label {
|
||||
opacity: 1;
|
||||
color: var(--highlight-r);
|
||||
}
|
||||
|
||||
.node.selected {
|
||||
background-color: var(--selected);
|
||||
color: #fff;
|
||||
border-color: #059669;
|
||||
transform: translate(-50%, -50%) scale(1.15);
|
||||
box-shadow: 0 0 20px rgba(16, 185, 129, 0.4);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h2>Iterative Segment Tree</h2>
|
||||
|
||||
<div class="controls">
|
||||
<label>Range [L, R):</label>
|
||||
<input type="number" id="lInput" value="1" min="0" max="7" />
|
||||
<span style="color: var(--text-muted)">to</span>
|
||||
<input type="number" id="rInput" value="7" min="1" max="8" />
|
||||
<button id="btn-start" onclick="initQuery()">Start Query</button>
|
||||
<button id="btn-step" onclick="nextStep()" disabled>Step ▶</button>
|
||||
<button id="btn-reset" onclick="resetSim()">Reset</button>
|
||||
</div>
|
||||
|
||||
<div class="info-panel">
|
||||
<div class="explanation" id="explanation">
|
||||
Ready. Set the range and click "Start Query".
|
||||
</div>
|
||||
<div class="result-box" id="result-box">
|
||||
<small style="width: 100%; color: var(--text-muted); margin-bottom: 5px"
|
||||
>Accumulator:</small
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="viz-container"></div>
|
||||
|
||||
<script>
|
||||
const n = 8;
|
||||
const width = 800;
|
||||
const height = 400;
|
||||
const levelGap = 90;
|
||||
|
||||
let treeData = [];
|
||||
let coords = {};
|
||||
let l, r;
|
||||
let isRunning = false;
|
||||
let stepState = 0;
|
||||
|
||||
// --- Initialization ---
|
||||
|
||||
function initTreeData() {
|
||||
treeData = Array(2 * n).fill(0);
|
||||
for (let i = 0; i < n; i++) treeData[n + i] = i + 1;
|
||||
for (let i = n - 1; i > 0; i--)
|
||||
treeData[i] = treeData[2 * i] + treeData[2 * i + 1];
|
||||
}
|
||||
|
||||
function calculateCoords() {
|
||||
const segmentWidth = width / n;
|
||||
for (let i = 0; i < n; i++) {
|
||||
let idx = n + i;
|
||||
coords[idx] = {
|
||||
x: i * segmentWidth + segmentWidth / 2,
|
||||
y: height - 60,
|
||||
};
|
||||
}
|
||||
for (let i = n - 1; i > 0; i--) {
|
||||
const leftChild = coords[2 * i];
|
||||
const rightChild = coords[2 * i + 1];
|
||||
coords[i] = {
|
||||
x: (leftChild.x + rightChild.x) / 2,
|
||||
y: leftChild.y - levelGap,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function renderNodes() {
|
||||
const container = document.getElementById("viz-container");
|
||||
container.innerHTML = "";
|
||||
for (let i = 1; i < 2 * n; i++) {
|
||||
const pos = coords[i];
|
||||
const node = document.createElement("div");
|
||||
node.className = "node";
|
||||
node.id = `node-${i}`;
|
||||
node.style.left = pos.x + "px";
|
||||
node.style.top = pos.y + "px";
|
||||
node.innerHTML = `
|
||||
<span class="idx">${i}</span>
|
||||
${treeData[i]}
|
||||
<span class="ptr-label" id="ptr-${i}"></span>
|
||||
`;
|
||||
container.appendChild(node);
|
||||
}
|
||||
}
|
||||
|
||||
// --- Visual Helpers ---
|
||||
|
||||
function setPointer(idx, label) {
|
||||
const node = document.getElementById(`node-${idx}`);
|
||||
const lbl = document.getElementById(`ptr-${idx}`);
|
||||
if (!node) return;
|
||||
if (label === "L") node.classList.add("ptr-l");
|
||||
if (label === "R") node.classList.add("ptr-r");
|
||||
if (lbl) lbl.textContent = label;
|
||||
}
|
||||
|
||||
function clearPointers() {
|
||||
for (let i = 1; i < 2 * n; i++) {
|
||||
const node = document.getElementById(`node-${i}`);
|
||||
const lbl = document.getElementById(`ptr-${i}`);
|
||||
node.classList.remove("ptr-l", "ptr-r");
|
||||
if (lbl) lbl.textContent = "";
|
||||
}
|
||||
}
|
||||
|
||||
function markSelected(idx) {
|
||||
const node = document.getElementById(`node-${idx}`);
|
||||
if (node) node.classList.add("selected");
|
||||
const box = document.getElementById("result-box");
|
||||
const badge = document.createElement("div");
|
||||
badge.className = "mini-badge";
|
||||
badge.textContent = `idx ${idx} (${treeData[idx]})`;
|
||||
box.appendChild(badge);
|
||||
}
|
||||
|
||||
function setMsg(html) {
|
||||
document.getElementById("explanation").innerHTML = html;
|
||||
}
|
||||
|
||||
// --- Logic Control ---
|
||||
|
||||
function initQuery() {
|
||||
resetSim(false);
|
||||
l = parseInt(document.getElementById("lInput").value) + n;
|
||||
r = parseInt(document.getElementById("rInput").value) + n;
|
||||
isRunning = true;
|
||||
stepState = 1;
|
||||
document.getElementById("btn-start").disabled = true;
|
||||
document.getElementById("btn-step").disabled = false;
|
||||
setMsg(`Initialized at leaves <b>L=${l}</b> and <b>R=${r}</b>.`);
|
||||
if (l < r) {
|
||||
setPointer(l, "L");
|
||||
setPointer(r, "R");
|
||||
}
|
||||
}
|
||||
|
||||
function nextStep() {
|
||||
if (!isRunning) return;
|
||||
|
||||
if (l >= r) {
|
||||
clearPointers();
|
||||
setMsg("<strong>L and R have met.</strong> Query Complete!");
|
||||
document.getElementById("btn-step").disabled = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (stepState === 1) {
|
||||
// Check L
|
||||
if (l % 2 === 1) {
|
||||
setMsg(
|
||||
`L=${l} is <span style="color:var(--highlight-l)">ODD</span> (Right Child).<br>We <b>PICK Node ${l}</b> and move L right.`,
|
||||
);
|
||||
markSelected(l);
|
||||
l++;
|
||||
} else {
|
||||
setMsg(
|
||||
`L=${l} is <span style="color:var(--text-muted)">EVEN</span> (Left Child).<br>Safe to move up.`,
|
||||
);
|
||||
}
|
||||
clearPointers();
|
||||
if (l < r) {
|
||||
setPointer(l, "L");
|
||||
setPointer(r, "R");
|
||||
}
|
||||
stepState = 2;
|
||||
} else if (stepState === 2) {
|
||||
// Check R
|
||||
if (r % 2 === 1) {
|
||||
r--;
|
||||
setMsg(
|
||||
`R is <span style="color:var(--highlight-r)">ODD</span>. Range ends after a Left Child.<br>We <b>PICK Node ${r}</b>.`,
|
||||
);
|
||||
markSelected(r);
|
||||
} else {
|
||||
setMsg(
|
||||
`R=${r} is <span style="color:var(--text-muted)">EVEN</span>. Boundary is clean.`,
|
||||
);
|
||||
}
|
||||
clearPointers();
|
||||
if (l < r) {
|
||||
setPointer(l, "L");
|
||||
setPointer(r, "R");
|
||||
}
|
||||
stepState = 3;
|
||||
} else if (stepState === 3) {
|
||||
// Move Up
|
||||
l = Math.floor(l / 2);
|
||||
r = Math.floor(r / 2);
|
||||
setMsg(
|
||||
`Move both pointers UP to parents.<br>New L=${l}, New R=${r}.`,
|
||||
);
|
||||
clearPointers();
|
||||
if (l < r) {
|
||||
setPointer(l, "L");
|
||||
setPointer(r, "R");
|
||||
}
|
||||
stepState = 1;
|
||||
}
|
||||
}
|
||||
|
||||
function resetSim(full = true) {
|
||||
isRunning = false;
|
||||
document.getElementById("btn-start").disabled = false;
|
||||
document.getElementById("btn-step").disabled = true;
|
||||
if (full) setMsg("Ready.");
|
||||
document.getElementById("result-box").innerHTML =
|
||||
'<small style="width:100%; color:var(--text-muted); margin-bottom:5px;">Accumulator:</small>';
|
||||
const nodes = document.querySelectorAll(".node");
|
||||
nodes.forEach((n) => {
|
||||
n.className = "node";
|
||||
n.querySelector(".ptr-label").textContent = "";
|
||||
});
|
||||
}
|
||||
|
||||
initTreeData();
|
||||
calculateCoords();
|
||||
renderNodes();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,14 @@
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
|
||||
// this isn't ideal
|
||||
#include "../include/iter_seg_tree.hpp"
|
||||
|
||||
int main() {
|
||||
auto sum = [](int a, int b) { return a + b; };
|
||||
|
||||
std::vector<int> vec = {1, 2, 3, 4};
|
||||
IterSegTree<int> st(vec, sum, 0);
|
||||
|
||||
std::cout << st.query(0, 2) << "\n";
|
||||
}
|
||||
Reference in New Issue
Block a user