<p class="hint">Array of 16 elements split into 4 blocks of size 4. <b>Update</b> changes one element (O(1) block rebuild). <b>Query</b> sums a range using whole blocks + partial ends (O(√n)).</p>
<div id="blocks-row" class="blocks-row"></div>
<div id="array-row" class="array-row"></div>
<div class="controls">
<div class="ctrl-group">
<label>Update index <span class="range-hint">(0–15)</span></label>
<div class="ctrl-row">
<input id="upd-idx" type="number" min="0" max="15" value="5" />
<input id="upd-val" type="number" min="0" max="99" value="42" />
<button id="btn-update" type="button">Update</button>
</div>
</div>
<div class="ctrl-group">
<label>Query range <span class="range-hint">(0–15)</span></label>
<div class="ctrl-row">
<input id="qry-l" type="number" min="0" max="15" value="2" />
<input id="qry-r" type="number" min="0" max="15" value="11" />
<button id="btn-query" type="button">Query</button>
</div>
</div>
</div>
<div class="status" id="status">Ready.</div>
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; color: #222; margin: 0; }
.hint { font-size: .88rem; color: #444; margin: 0 0 .8rem; line-height: 1.5; }
.blocks-row { display: flex; gap: 6px; margin-bottom: 4px; }
.block-sum {
flex: 1; height: 36px; border-radius: 8px 8px 0 0;
display: flex; align-items: center; justify-content: center;
font: 700 13px ui-monospace, monospace; color: #fff;
transition: background .2s;
}
.array-row { display: flex; gap: 2px; margin-bottom: .8rem; }
.cell {
flex: 1; height: 46px; border-radius: 0 0 6px 6px;
display: flex; align-items: center; justify-content: center;
font: 600 13px ui-monospace, monospace; border: 1.5px solid #cdd9e3;
background: #e8eef3; color: #1d3557;
transition: background .15s, color .15s;
}
.cell.highlight-partial { background: #fce07c; border-color: #d4a900; color: #5a3e00; }
.cell.highlight-full { background: #6ec87a; border-color: #3a8f48; color: #fff; }
.cell.updated { background: #e63946; border-color: #c92f3c; color: #fff; }
.block-sum.updated-blk { background: #c92f3c !important; }
.controls { display: flex; flex-wrap: wrap; gap: .8rem; margin-bottom: .6rem; }
.ctrl-group { display: flex; flex-direction: column; gap: .3rem; }
.ctrl-group label { font-size: .82rem; font-weight: 600; color: #1d3557; }
.ctrl-group .range-hint { font-weight: 400; color: #666; }
.ctrl-row { display: flex; gap: .4rem; align-items: center; }
input[type=number] {
width: 58px; padding: .35rem .4rem; border: 1px solid #adb1b8;
border-radius: 6px; font: 600 14px ui-monospace, monospace; text-align: center;
}
button {
font: 600 13px system-ui, sans-serif; padding: .4rem .8rem;
border: 1px solid #1d3557; background: #1d3557; color: #fff;
border-radius: 7px; cursor: pointer;
}
button:hover { background: #162840; }
.status { font-size: .95rem; font-weight: 600; min-height: 1.4em; color: #1d3557; }
.status.ok { color: #0a7d33; }
.status.bad { color: #c92f3c; }
const fmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// Sqrt Decomposition: range-sum with point updates.
// Array length N=16, block size B=4 (= sqrt(16)).
// Block sums allow O(B) = O(4) query + O(1) update.
const N = 16, B = 4;
const BLOCK_COLORS = ['#1d6fa4','#2a8a5e','#8a4f2a','#6b3a7d'];
// Initialize array with some values
const arr = [7,3,9,2, 5,1,8,4, 6,10,2,7, 3,5,1,9];
const blockSum = new Array(N/B).fill(0);
function rebuildBlockSum(b) {
let s = 0;
for (let i = b*B; i < (b+1)*B; i++) s += arr[i];
blockSum[b] = s;
}
// Build all block sums initially
for (let b = 0; b < N/B; b++) rebuildBlockSum(b);
function render(highlightCells, highlightBlocks, updatedIdx) {
const blocksRow = document.getElementById('blocks-row');
const arrayRow = document.getElementById('array-row');
blocksRow.innerHTML = '';
arrayRow.innerHTML = '';
for (let b = 0; b < N/B; b++) {
const div = document.createElement('div');
div.className = 'block-sum' + (highlightBlocks && highlightBlocks.has(b) ? ' updated-blk' : '');
div.style.background = highlightBlocks && highlightBlocks.has(b) ? '#3a8f48' : BLOCK_COLORS[b];
div.textContent = 'Σ' + blockSum[b];
blocksRow.appendChild(div);
}
for (let i = 0; i < N; i++) {
const div = document.createElement('div');
let cls = 'cell';
if (updatedIdx === i) cls += ' updated';
else if (highlightCells && highlightCells.type === 'partial' && highlightCells.set.has(i)) cls += ' highlight-partial';
else if (highlightCells && highlightCells.type === 'full' && highlightCells.set.has(i)) cls += ' highlight-full';
div.className = cls;
div.textContent = arr[i];
arrayRow.appendChild(div);
}
}
function setStatus(msg, cls) {
const s = document.getElementById('status');
s.textContent = msg; s.className = 'status ' + (cls||'');
}
document.getElementById('btn-update').addEventListener('click', () => {
const idx = parseInt(document.getElementById('upd-idx').value);
const val = parseInt(document.getElementById('upd-val').value);
if (isNaN(idx)||idx<0||idx>15||isNaN(val)||val<0||val>99) {
setStatus('Index 0–15 and value 0–99 please.','bad'); return;
}
arr[idx] = val;
const b = Math.floor(idx/B);
rebuildBlockSum(b);
render(null, new Set([b]), idx);
setStatus(fmt('Updated a[%idx%] = %val%. Only block %b% sum rebuilt — O(B) = O(4).', { idx: idx, val: val, b: b }), 'ok');
console.log(fmt('update idx=%idx% val=%val% blockSums=%blockSum%', { idx: idx, val: val, blockSum: blockSum.join(',') }));
});
document.getElementById('btn-query').addEventListener('click', () => {
const l = parseInt(document.getElementById('qry-l').value);
const r = parseInt(document.getElementById('qry-r').value);
if (isNaN(l)||isNaN(r)||l<0||r>15||l>r) {
setStatus('Need 0 ≤ l ≤ r ≤ 15.','bad'); return;
}
// Walk the range: partial left block, full middle blocks, partial right block
const partialSet = new Set();
const fullBlockSet = new Set();
let sum = 0, steps = 0;
let bL = Math.floor(l/B), bR = Math.floor(r/B);
if (bL === bR) {
// Same block — walk individually
for (let i = l; i <= r; i++) { sum += arr[i]; steps++; partialSet.add(i); }
} else {
// Left partial
const leftEnd = (bL+1)*B - 1;
for (let i = l; i <= leftEnd; i++) { sum += arr[i]; steps++; partialSet.add(i); }
// Full middle blocks
for (let b = bL+1; b < bR; b++) { sum += blockSum[b]; steps++; fullBlockSet.add(b);
for (let i = b*B; i < (b+1)*B; i++) partialSet.add(i); /* visualize */ }
// Right partial
for (let i = bR*B; i <= r; i++) { sum += arr[i]; steps++; partialSet.add(i); }
}
const allCells = new Set([...partialSet]);
const highlightCells = { type: 'partial', set: allCells };
render(highlightCells, fullBlockSet, null);
// Color full-block cells distinctly
const cells = document.querySelectorAll('.cell');
fullBlockSet.forEach(b => {
for (let i = b*B; i < (b+1)*B; i++) {
cells[i].className = 'cell highlight-full';
}
});
setStatus(fmt('Sum a[%l%…%r%] = %sum% (%steps% step%v%, yellow=partial, green=full block).', { l: l, r: r, sum: sum, steps: steps, v: steps!==1?'s':'' }), 'ok');
console.log(fmt('query l=%l% r=%r% sum=%sum% steps=%steps%', { l: l, r: r, sum: sum, steps: steps }));
});
render(null, null, null);
<p class="hint">Array of 16 elements split into 4 blocks of size 4. <b>Update</b> changes one element (O(1) block rebuild). <b>Query</b> sums a range using whole blocks + partial ends (O(√n)).</p>
<div id="blocks-row" class="blocks-row"></div>
<div id="array-row" class="array-row"></div>
<div class="controls">
<div class="ctrl-group">
<label>Update index <span class="range-hint">(0–15)</span></label>
<div class="ctrl-row">
<input id="upd-idx" type="number" min="0" max="15" value="5" />
<input id="upd-val" type="number" min="0" max="99" value="42" />
<button id="btn-update" type="button">Update</button>
</div>
</div>
<div class="ctrl-group">
<label>Query range <span class="range-hint">(0–15)</span></label>
<div class="ctrl-row">
<input id="qry-l" type="number" min="0" max="15" value="2" />
<input id="qry-r" type="number" min="0" max="15" value="11" />
<button id="btn-query" type="button">Query</button>
</div>
</div>
</div>
<div class="status" id="status">Ready.</div>
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; color: #222; margin: 0; }
.hint { font-size: .88rem; color: #444; margin: 0 0 .8rem; line-height: 1.5; }
.blocks-row { display: flex; gap: 6px; margin-bottom: 4px; }
.block-sum {
flex: 1; height: 36px; border-radius: 8px 8px 0 0;
display: flex; align-items: center; justify-content: center;
font: 700 13px ui-monospace, monospace; color: #fff;
transition: background .2s;
}
.array-row { display: flex; gap: 2px; margin-bottom: .8rem; }
.cell {
flex: 1; height: 46px; border-radius: 0 0 6px 6px;
display: flex; align-items: center; justify-content: center;
font: 600 13px ui-monospace, monospace; border: 1.5px solid #cdd9e3;
background: #e8eef3; color: #1d3557;
transition: background .15s, color .15s;
}
.cell.highlight-partial { background: #fce07c; border-color: #d4a900; color: #5a3e00; }
.cell.highlight-full { background: #6ec87a; border-color: #3a8f48; color: #fff; }
.cell.updated { background: #e63946; border-color: #c92f3c; color: #fff; }
.block-sum.updated-blk { background: #c92f3c !important; }
.controls { display: flex; flex-wrap: wrap; gap: .8rem; margin-bottom: .6rem; }
.ctrl-group { display: flex; flex-direction: column; gap: .3rem; }
.ctrl-group label { font-size: .82rem; font-weight: 600; color: #1d3557; }
.ctrl-group .range-hint { font-weight: 400; color: #666; }
.ctrl-row { display: flex; gap: .4rem; align-items: center; }
input[type=number] {
width: 58px; padding: .35rem .4rem; border: 1px solid #adb1b8;
border-radius: 6px; font: 600 14px ui-monospace, monospace; text-align: center;
}
button {
font: 600 13px system-ui, sans-serif; padding: .4rem .8rem;
border: 1px solid #1d3557; background: #1d3557; color: #fff;
border-radius: 7px; cursor: pointer;
}
button:hover { background: #162840; }
.status { font-size: .95rem; font-weight: 600; min-height: 1.4em; color: #1d3557; }
.status.ok { color: #0a7d33; }
.status.bad { color: #c92f3c; }
const fmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// Sqrt Decomposition: range-sum with point updates.
// Array length N=16, block size B=4 (= sqrt(16)).
// Block sums allow O(B) = O(4) query + O(1) update.
const N = 16, B = 4;
const BLOCK_COLORS = ['#1d6fa4','#2a8a5e','#8a4f2a','#6b3a7d'];
// Initialize array with some values
const arr = [7,3,9,2, 5,1,8,4, 6,10,2,7, 3,5,1,9];
const blockSum = new Array(N/B).fill(0);
function rebuildBlockSum(b) {
let s = 0;
for (let i = b*B; i < (b+1)*B; i++) s += arr[i];
blockSum[b] = s;
}
// Build all block sums initially
for (let b = 0; b < N/B; b++) rebuildBlockSum(b);
function render(highlightCells, highlightBlocks, updatedIdx) {
const blocksRow = document.getElementById('blocks-row');
const arrayRow = document.getElementById('array-row');
blocksRow.innerHTML = '';
arrayRow.innerHTML = '';
for (let b = 0; b < N/B; b++) {
const div = document.createElement('div');
div.className = 'block-sum' + (highlightBlocks && highlightBlocks.has(b) ? ' updated-blk' : '');
div.style.background = highlightBlocks && highlightBlocks.has(b) ? '#3a8f48' : BLOCK_COLORS[b];
div.textContent = 'Σ' + blockSum[b];
blocksRow.appendChild(div);
}
for (let i = 0; i < N; i++) {
const div = document.createElement('div');
let cls = 'cell';
if (updatedIdx === i) cls += ' updated';
else if (highlightCells && highlightCells.type === 'partial' && highlightCells.set.has(i)) cls += ' highlight-partial';
else if (highlightCells && highlightCells.type === 'full' && highlightCells.set.has(i)) cls += ' highlight-full';
div.className = cls;
div.textContent = arr[i];
arrayRow.appendChild(div);
}
}
function setStatus(msg, cls) {
const s = document.getElementById('status');
s.textContent = msg; s.className = 'status ' + (cls||'');
}
document.getElementById('btn-update').addEventListener('click', () => {
const idx = parseInt(document.getElementById('upd-idx').value);
const val = parseInt(document.getElementById('upd-val').value);
if (isNaN(idx)||idx<0||idx>15||isNaN(val)||val<0||val>99) {
setStatus('Index 0–15 and value 0–99 please.','bad'); return;
}
arr[idx] = val;
const b = Math.floor(idx/B);
rebuildBlockSum(b);
render(null, new Set([b]), idx);
setStatus(fmt('Updated a[%idx%] = %val%. Only block %b% sum rebuilt — O(B) = O(4).', { idx: idx, val: val, b: b }), 'ok');
console.log(fmt('update idx=%idx% val=%val% blockSums=%blockSum%', { idx: idx, val: val, blockSum: blockSum.join(',') }));
});
document.getElementById('btn-query').addEventListener('click', () => {
const l = parseInt(document.getElementById('qry-l').value);
const r = parseInt(document.getElementById('qry-r').value);
if (isNaN(l)||isNaN(r)||l<0||r>15||l>r) {
setStatus('Need 0 ≤ l ≤ r ≤ 15.','bad'); return;
}
// Walk the range: partial left block, full middle blocks, partial right block
const partialSet = new Set();
const fullBlockSet = new Set();
let sum = 0, steps = 0;
let bL = Math.floor(l/B), bR = Math.floor(r/B);
if (bL === bR) {
// Same block — walk individually
for (let i = l; i <= r; i++) { sum += arr[i]; steps++; partialSet.add(i); }
} else {
// Left partial
const leftEnd = (bL+1)*B - 1;
for (let i = l; i <= leftEnd; i++) { sum += arr[i]; steps++; partialSet.add(i); }
// Full middle blocks
for (let b = bL+1; b < bR; b++) { sum += blockSum[b]; steps++; fullBlockSet.add(b);
for (let i = b*B; i < (b+1)*B; i++) partialSet.add(i); /* visualize */ }
// Right partial
for (let i = bR*B; i <= r; i++) { sum += arr[i]; steps++; partialSet.add(i); }
}
const allCells = new Set([...partialSet]);
const highlightCells = { type: 'partial', set: allCells };
render(highlightCells, fullBlockSet, null);
// Color full-block cells distinctly
const cells = document.querySelectorAll('.cell');
fullBlockSet.forEach(b => {
for (let i = b*B; i < (b+1)*B; i++) {
cells[i].className = 'cell highlight-full';
}
});
setStatus(fmt('Sum a[%l%…%r%] = %sum% (%steps% step%v%, yellow=partial, green=full block).', { l: l, r: r, sum: sum, steps: steps, v: steps!==1?'s':'' }), 'ok');
console.log(fmt('query l=%l% r=%r% sum=%sum% steps=%steps%', { l: l, r: r, sum: sum, steps: steps }));
});
render(null, null, null);
Comments
Loading comments...