<p class="hint">An 8-element array sits at the bottom; each node above stores the <b>sum</b> of the leaves beneath it. <b>Click a leaf</b> to add 1 to it — only the nodes on the path to the root change. Then pick a range and press <b>Query</b>: the answer comes from a few covering nodes, not from scanning the cells.</p>
<div id="tree" class="tree"></div>
<div class="controls">
<label>Range <select id="lo"></select> … <select id="hi"></select></label>
<button id="query" type="button">Query sum</button>
<button id="reset" type="button" class="ghost">Reset</button>
</div>
<div class="status" id="status">Click a leaf to update, or run a range query.</div>
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; color: #222; margin: 0; }
.hint { font-size: .9rem; color: #444; margin: 0 0 .8rem; line-height: 1.45; }
.tree { display: flex; flex-direction: column; gap: 8px; align-items: stretch; }
.level { display: flex; gap: 6px; justify-content: center; }
.node { flex: 1 1 0; min-width: 0; height: 38px; display: flex; flex-direction: column;
align-items: center; justify-content: center; border-radius: 8px;
background: #e8eef3; color: #1d3557; border: 1px solid #cdd9e3;
font: 700 14px ui-monospace, monospace; transition: all .25s; }
.node small { font: 500 9px system-ui, sans-serif; color: #6b7c8f; }
.node.leaf { cursor: pointer; background: #d7e3ee; }
.node.leaf:hover { background: #c4d6e6; }
.node.touched { background: #ffd166; border-color: #e0a92e; color: #5a3d00; }
.node.cover { background: #06d6a0; border-color: #04a87c; color: #003124; }
.status { font-size: .98rem; font-weight: 600; margin: .7rem 0 0; min-height: 1.4em; color: #1d3557; }
.controls { display: flex; gap: .5rem; align-items: center; flex-wrap: wrap; margin-top: .8rem; }
select { font: 600 13px ui-monospace, monospace; padding: .25rem .4rem; border-radius: 6px; border: 1px solid #adb1b8; }
button { font: 600 14px system-ui, sans-serif; padding: .45rem .9rem; border: 1px solid #1d3557;
background: #1d3557; color: #fff; border-radius: 8px; cursor: pointer; }
button.ghost { background: #fff; color: #1d3557; }
const fmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// Segment tree over n = 8 leaves stored in a 1-indexed array of size 2n.
// tree[1] is the root; node i has children 2i and 2i+1; leaves are tree[n..2n-1].
// build O(n); point update and range query each O(log n): they touch at most a
// constant number of nodes per level, and the tree has height ceil(log2 n).
const n = 8;
let arr = [3, 1, 4, 1, 5, 9, 2, 6];
const tree = new Array(2 * n).fill(0);
function build() {
for (let i = 0; i < n; i++) tree[n + i] = arr[i];
for (let i = n - 1; i >= 1; i--) tree[i] = tree[2 * i] + tree[2 * i + 1];
}
// Update leaf index p (0-based) to value v; returns the set of node ids touched.
function update(p, v) {
const path = new Set();
let i = p + n; tree[i] = v; path.add(i);
for (i = Math.floor(i / 2); i >= 1; i = Math.floor(i / 2)) {
tree[i] = tree[2 * i] + tree[2 * i + 1]; path.add(i);
}
return path;
}
// Range sum over [l, r] inclusive (0-based); returns {sum, cover:Set of node ids}.
function query(l, r) {
let lo = l + n, hi = r + n + 1, sum = 0; const cover = new Set();
while (lo < hi) {
if (lo & 1) { sum += tree[lo]; cover.add(lo); lo++; }
if (hi & 1) { hi--; sum += tree[hi]; cover.add(hi); }
lo >>= 1; hi >>= 1;
}
return { sum, cover };
}
// ---- rendering ----
// Place node ids by level: level d holds ids [2^d .. 2^(d+1)-1]. n=8 => depths 0..3.
const depth = Math.log2(n); // 3
function render(highlight) {
const el = document.getElementById('tree'); el.innerHTML = '';
for (let d = 0; d <= depth; d++) {
const row = document.createElement('div'); row.className = 'level';
const start = 1 << d, end = (1 << (d + 1)) - 1;
for (let id = start; id <= end; id++) {
const node = document.createElement('div');
const isLeaf = id >= n;
node.className = 'node' + (isLeaf ? ' leaf' : '');
if (highlight && highlight.touched && highlight.touched.has(id)) node.classList.add('touched');
if (highlight && highlight.cover && highlight.cover.has(id)) node.classList.add('cover');
node.innerHTML = tree[id] + (isLeaf ? '<small>a[' + (id - n) + ']</small>' : '');
if (isLeaf) {
const p = id - n;
node.addEventListener('click', () => {
const touched = update(p, tree[id] + 1); arr[p] = tree[id];
render({ touched });
setStatus(fmt('Updated a[%p%] = %v% — touched %size% of %v2% nodes (about log₂ %n% + 1).', { p: p, v: tree[id], size: touched.size, v2: 2 * n - 1, n: n }));
});
}
row.appendChild(node);
}
el.appendChild(row);
}
}
function setStatus(t) { document.getElementById('status').textContent = t; }
function fillSelects() {
const lo = document.getElementById('lo'), hi = document.getElementById('hi');
for (let i = 0; i < n; i++) {
lo.add(new Option(i, i)); hi.add(new Option(i, i));
}
lo.value = 2; hi.value = 6;
}
function runQuery() {
let l = +document.getElementById('lo').value, r = +document.getElementById('hi').value;
if (l > r) { const t = l; l = r; r = t; document.getElementById('lo').value = l; document.getElementById('hi').value = r; }
const { sum, cover } = query(l, r);
render({ cover });
setStatus(fmt('sum(a[%l%..%r%]) = %sum% from %size% covering node(s) — not %v%%v2% element reads.', { l: l, r: r, sum: sum, size: cover.size, v: r - l, v2: 1 }));
console.log('query', l, r, '=>', sum, 'cover ids', [...cover]);
}
function reset() {
arr = [3, 1, 4, 1, 5, 9, 2, 6]; build(); render(null);
setStatus('Click a leaf to update, or run a range query.');
}
document.getElementById('query').addEventListener('click', runQuery);
document.getElementById('reset').addEventListener('click', reset);
fillSelects(); build(); render(null);
<p class="hint">An 8-element array sits at the bottom; each node above stores the <b>sum</b> of the leaves beneath it. <b>Click a leaf</b> to add 1 to it — only the nodes on the path to the root change. Then pick a range and press <b>Query</b>: the answer comes from a few covering nodes, not from scanning the cells.</p>
<div id="tree" class="tree"></div>
<div class="controls">
<label>Range <select id="lo"></select> … <select id="hi"></select></label>
<button id="query" type="button">Query sum</button>
<button id="reset" type="button" class="ghost">Reset</button>
</div>
<div class="status" id="status">Click a leaf to update, or run a range query.</div>
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; color: #222; margin: 0; }
.hint { font-size: .9rem; color: #444; margin: 0 0 .8rem; line-height: 1.45; }
.tree { display: flex; flex-direction: column; gap: 8px; align-items: stretch; }
.level { display: flex; gap: 6px; justify-content: center; }
.node { flex: 1 1 0; min-width: 0; height: 38px; display: flex; flex-direction: column;
align-items: center; justify-content: center; border-radius: 8px;
background: #e8eef3; color: #1d3557; border: 1px solid #cdd9e3;
font: 700 14px ui-monospace, monospace; transition: all .25s; }
.node small { font: 500 9px system-ui, sans-serif; color: #6b7c8f; }
.node.leaf { cursor: pointer; background: #d7e3ee; }
.node.leaf:hover { background: #c4d6e6; }
.node.touched { background: #ffd166; border-color: #e0a92e; color: #5a3d00; }
.node.cover { background: #06d6a0; border-color: #04a87c; color: #003124; }
.status { font-size: .98rem; font-weight: 600; margin: .7rem 0 0; min-height: 1.4em; color: #1d3557; }
.controls { display: flex; gap: .5rem; align-items: center; flex-wrap: wrap; margin-top: .8rem; }
select { font: 600 13px ui-monospace, monospace; padding: .25rem .4rem; border-radius: 6px; border: 1px solid #adb1b8; }
button { font: 600 14px system-ui, sans-serif; padding: .45rem .9rem; border: 1px solid #1d3557;
background: #1d3557; color: #fff; border-radius: 8px; cursor: pointer; }
button.ghost { background: #fff; color: #1d3557; }
const fmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// Segment tree over n = 8 leaves stored in a 1-indexed array of size 2n.
// tree[1] is the root; node i has children 2i and 2i+1; leaves are tree[n..2n-1].
// build O(n); point update and range query each O(log n): they touch at most a
// constant number of nodes per level, and the tree has height ceil(log2 n).
const n = 8;
let arr = [3, 1, 4, 1, 5, 9, 2, 6];
const tree = new Array(2 * n).fill(0);
function build() {
for (let i = 0; i < n; i++) tree[n + i] = arr[i];
for (let i = n - 1; i >= 1; i--) tree[i] = tree[2 * i] + tree[2 * i + 1];
}
// Update leaf index p (0-based) to value v; returns the set of node ids touched.
function update(p, v) {
const path = new Set();
let i = p + n; tree[i] = v; path.add(i);
for (i = Math.floor(i / 2); i >= 1; i = Math.floor(i / 2)) {
tree[i] = tree[2 * i] + tree[2 * i + 1]; path.add(i);
}
return path;
}
// Range sum over [l, r] inclusive (0-based); returns {sum, cover:Set of node ids}.
function query(l, r) {
let lo = l + n, hi = r + n + 1, sum = 0; const cover = new Set();
while (lo < hi) {
if (lo & 1) { sum += tree[lo]; cover.add(lo); lo++; }
if (hi & 1) { hi--; sum += tree[hi]; cover.add(hi); }
lo >>= 1; hi >>= 1;
}
return { sum, cover };
}
// ---- rendering ----
// Place node ids by level: level d holds ids [2^d .. 2^(d+1)-1]. n=8 => depths 0..3.
const depth = Math.log2(n); // 3
function render(highlight) {
const el = document.getElementById('tree'); el.innerHTML = '';
for (let d = 0; d <= depth; d++) {
const row = document.createElement('div'); row.className = 'level';
const start = 1 << d, end = (1 << (d + 1)) - 1;
for (let id = start; id <= end; id++) {
const node = document.createElement('div');
const isLeaf = id >= n;
node.className = 'node' + (isLeaf ? ' leaf' : '');
if (highlight && highlight.touched && highlight.touched.has(id)) node.classList.add('touched');
if (highlight && highlight.cover && highlight.cover.has(id)) node.classList.add('cover');
node.innerHTML = tree[id] + (isLeaf ? '<small>a[' + (id - n) + ']</small>' : '');
if (isLeaf) {
const p = id - n;
node.addEventListener('click', () => {
const touched = update(p, tree[id] + 1); arr[p] = tree[id];
render({ touched });
setStatus(fmt('Updated a[%p%] = %v% — touched %size% of %v2% nodes (about log₂ %n% + 1).', { p: p, v: tree[id], size: touched.size, v2: 2 * n - 1, n: n }));
});
}
row.appendChild(node);
}
el.appendChild(row);
}
}
function setStatus(t) { document.getElementById('status').textContent = t; }
function fillSelects() {
const lo = document.getElementById('lo'), hi = document.getElementById('hi');
for (let i = 0; i < n; i++) {
lo.add(new Option(i, i)); hi.add(new Option(i, i));
}
lo.value = 2; hi.value = 6;
}
function runQuery() {
let l = +document.getElementById('lo').value, r = +document.getElementById('hi').value;
if (l > r) { const t = l; l = r; r = t; document.getElementById('lo').value = l; document.getElementById('hi').value = r; }
const { sum, cover } = query(l, r);
render({ cover });
setStatus(fmt('sum(a[%l%..%r%]) = %sum% from %size% covering node(s) — not %v%%v2% element reads.', { l: l, r: r, sum: sum, size: cover.size, v: r - l, v2: 1 }));
console.log('query', l, r, '=>', sum, 'cover ids', [...cover]);
}
function reset() {
arr = [3, 1, 4, 1, 5, 9, 2, 6]; build(); render(null);
setStatus('Click a leaf to update, or run a range query.');
}
document.getElementById('query').addEventListener('click', runQuery);
document.getElementById('reset').addEventListener('click', reset);
fillSelects(); build(); render(null);
Comments
Loading comments...