[창작 문제] 전시실 예상 인원판 교체
전시실별 예상 인원을 한 칸씩 교체하며 구간 합계를 구합니다. replace는 증가량이 아니라 새로운 값을 지정합니다.

이 문제는 BlogFlow 자료구조 학습용으로 독립 창작했습니다. 외부 문제의 지문이나 테스트를 옮기지 않았습니다.
문제
가로로 놓인 전시실의 예상 인원판 values가 주어집니다. replace 작업은 한 칸의 예상치를 새 값으로 바꾸고, total 작업은 지정 구간의 예상치 합을 묻습니다. 변경이 즉시 다음 조회에 반영되도록 답하세요. 음수는 이전 예측에 대한 조정 결과를 나타낼 수 있으므로 허용합니다.
관련 구현 설명: JavaScript 반복형 세그먼트 트리: 구간 합·단일 대입·결합 순서
입력·출력과 제약
JSON 객체 {values,ops}. 작업은 ["replace",index,value] 또는 ["total",left,right]. 구간은 [left,right)입니다.
total 작업의 답을 순서대로 담은 JSON 배열입니다. 조회가 없으면 []입니다.
배열 길이와 작업 수는 각각 0~100,000입니다. 모든 값은 정수이고 절댓값 1,000,000 이하입니다. replace는 유효한 원소 인덱스만 사용하며 total은 0 ≤ left ≤ right ≤ n을 지킵니다. 모든 부분합은 안전한 정수 범위에 있습니다.
입력은 위 계약을 만족한다고 가정합니다. 온라인 채점기는 제공하지 않으며 로컬 Node.js에서 JSON 입력으로 실행합니다.
예시
예시 1 입력:
{"values":[4,2,7,1,3],"ops":[["total",1,5],["replace",2,5],["total",1,5],["total",0,0]]}
예시 1 출력:
[13,11,0]
예시 2 입력:
{"values":[],"ops":[]}
예시 2 출력:
[]
힌트
교체한 잎의 조상만 재계산하세요. 조회할 때 왼쪽과 오른쪽에서 수집한 블록을 서로 다른 누산기에 담으면 결합 순서가 보존됩니다.
정답과 해설
정답 코드와 해설 펼치기
Node.js 24 CommonJS용 전체 풀이입니다. segment-tree-problem.cjs로 저장하세요. 클래스 선언, solve 함수, 표준 입력 처리가 모두 들어 있어 가이드 파일을 별도로 가져오지 않습니다. 예시 입력을 input.json에 저장하고 터미널에서 실행합니다.
Windows PowerShell:
Get-Content -Raw -Encoding UTF8 .input.json | node .segment-tree-problem.cjs
macOS / Linux:
node segment-tree-problem.cjs < input.json
class SegmentTree {
constructor(values, combine = (a, b) => a + b, identity = 0) {
if (!Array.isArray(values) || values.length > 1_000_000) {
throw new RangeError('길이 1,000,000 이하 배열이 필요합니다.');
}
if (typeof combine !== 'function') throw new TypeError('결합 함수가 필요합니다.');
this.n = values.length;
this.combine = combine;
this.identity = identity;
this.base = 1;
while (this.base < this.n) this.base *= 2;
this.tree = Array(this.base * 2).fill(identity);
for (let i = 0; i < this.n; i++) this.tree[this.base + i] = values[i];
for (let i = this.base - 1; i > 0; i--) {
this.tree[i] = combine(this.tree[i * 2], this.tree[i * 2 + 1]);
}
}
boundary(i) {
if (!Number.isInteger(i) || i < 0 || i > this.n) throw new RangeError('잘못된 경계입니다.');
}
set(index, value) {
this.boundary(index);
if (index === this.n) throw new RangeError('갱신 인덱스는 n보다 작아야 합니다.');
let p = this.base + index;
this.tree[p] = value;
while (p > 1) {
p = Math.floor(p / 2);
this.tree[p] = this.combine(this.tree[p * 2], this.tree[p * 2 + 1]);
}
}
query(left, right) {
this.boundary(left);
this.boundary(right);
if (left > right) throw new RangeError('left는 right 이하여야 합니다.');
let l = left + this.base, r = right + this.base;
let accLeft = this.identity, accRight = this.identity;
while (l < r) {
if (l % 2 === 1) accLeft = this.combine(accLeft, this.tree[l++]);
if (r % 2 === 1) accRight = this.combine(this.tree[--r], accRight);
l = Math.floor(l / 2);
r = Math.floor(r / 2);
}
return this.combine(accLeft, accRight);
}
}
function solve({ values, ops }) {
const tree = new SegmentTree(values), out = [];
for (const [type, a, b] of ops) {
if (type === 'replace') tree.set(a, b);
else if (type === 'total') out.push(tree.query(a, b));
else throw new Error('알 수 없는 작업입니다.');
}
return out;
}
module.exports = { solve };
if (require.main === module) {
const input = JSON.parse(require('node:fs').readFileSync(0, 'utf8'));
console.log(JSON.stringify(solve(input)));
}
첫 total은 2+7+1+3=13입니다. replace 이후 원본은 [4,2,5,1,3]이므로 같은 구간은 11입니다. 마지막 조회는 빈 구간이므로 항등원 0을 출력합니다. solve는 세그먼트 트리를 한 번 만들고 replace를 set에, total을 query에 대응시킵니다. 새 예상치를 delta로 처리하지 않는 것이 Fenwick 증가 예제와 다른 점입니다.
base는 n 이상인 가장 작은 2의 거듭제곱입니다. 원본 values[i]는 tree[base+i]에 놓고 나머지 잎은 항등원으로 채웁니다. 각 내부 노드는 왼쪽 자식과 오른쪽 자식을 그 순서로 결합합니다. 따라서 루트는 패딩을 포함해도 실제 배열 전체와 같은 결과를 갖습니다.
조회는 l,r을 잎 위치로 옮긴 뒤 아직 처리하지 않은 [l,r) 영역을 좁힙니다. l이 오른쪽 자식이면 왼쪽 형제가 범위 밖이므로 tree[l]만 받아 l을 하나 늘립니다. r이 홀수이면 r−1이 범위 안의 마지막 왼쪽 자식이므로 먼저 r을 줄이고 그 노드를 받습니다. 나머지는 완전한 형제 쌍이라 부모로 올릴 수 있습니다.
빌드 O(n+1), q개 작업 최악 O(q log(n+1)), 트리 O(n+1), 결과 O(q) 공간입니다. 숫자 합이 O(1)인 계약에서의 분석입니다.
표본과 경계를 직접 확인하려면 다음 코드를 segment-tree-check.cjs로 저장하고 같은 폴더에서 node segment-tree-check.cjs를 실행하세요. assert가 맞으면 출력 없이 종료하고, 다르면 예외와 함께 실패합니다.
const assert = require('node:assert/strict');
const { solve } = require('./segment-tree-problem.cjs');
assert.deepEqual(
solve({
"values": [
4,
2,
7,
1,
3
],
"ops": [
[
"total",
1,
5
],
[
"replace",
2,
5
],
[
"total",
1,
5
],
[
"total",
0,
0
]
]
}),
[13,11,0],
);
assert.deepEqual(
solve({
"values": [],
"ops": []
}),
[],
);
연결 학습과 공식 자료
JavaScript 반복형 세그먼트 트리: 구간 합·단일 대입·결합 순서에서 연산별 이유와 전체 복잡도를 이어서 확인할 수 있습니다. 다른 형식의 공식 연습은 LeetCode 307 – Range Sum Query – Mutable를 참고하세요. 외부 문제의 정답이나 지문은 이 페이지에 복제하지 않았습니다.
공식 자료 확인일: 2026년 9월 10일. 구현은 이 글의 JavaScript 계약에 맞춰 독립적으로 작성했습니다. 다른 언어 라이브러리의 API와 숫자 범위가 그대로 적용되는 것은 아닙니다.
풀이 전에 확인할 순서
- 입력값과 출력값을 한 문장으로 다시 적습니다.
- 반복할 대상과 비교·저장할 값을 정합니다.
- 필요한 자료구조와 시간복잡도를 예상합니다.
- 코드를 보기 전에 손으로 작은 예제를 계산합니다.
힌트: 코딩테스트 JS 세그먼트 트리: 단일 대입과 구간 합
정답 코드를 바로 따라 쓰기보다, 본문에서 값이 갱신되는 조건과 반복 범위를 먼저 찾으세요. 반복 한 번마다 반드시 유지되어야 하는 값이 무엇인지 적으면 풀이의 중심 변수를 고르기 쉽습니다.
테스트 확인
- 가능한 가장 작은 입력
- 같은 값이나 문자가 반복되는 입력
- 정답이 처음 또는 마지막 위치에서 결정되는 입력
- 입력 제한에 가까운 경우의 실행 시간
확인 결과: 본문의 예제뿐 아니라 위 경계 사례에서도 예상값과 실제 출력이 같아야 풀이가 완료됩니다.
이 글이 도움이 되었나요?
코딩테스트 JavaScript 학습 순서
필수 49개 · 전체 49개
읽음 기록 관리
전체 과정 목차 (49개)
- 필수 길잡이 · 코딩테스트 JS 자료구조 로드맵: 배열, 해시, 스택, 투 포인터 순서
- 필수 학습 · 세 수 중 최솟값 JavaScript 조건문 풀이 정리
- 필수 학습 · 삼각형 판별하기 JavaScript 풀이
- 필수 학습 · 연필 개수 JavaScript 풀이
- 필수 학습 · 1부터 N까지 합 출력하기 JavaScript 풀이
- 필수 학습 · 최솟값 구하기 JavaScript 풀이|배열 순회와 비교 갱신 원리
- 필수 학습 · 홀수 JavaScript 풀이: 조건 판별과 결과 처리 정리
- 필수 학습 · 10부제 JavaScript 풀이: 끝자리 비교로 위반 차량 수 세기
- 필수 학습 · A를 #으로 JavaScript 풀이: 문자열 순회와 치환
- 필수 학습 · 문자 찾기 JavaScript 풀이: 문자열 순회로 개수 세기
- 필수 학습 · 대문자 찾기 JavaScript 풀이
- 필수 학습 · 대문자로 통일 JavaScript 풀이
- 필수 학습 · 대소문자 변환 JavaScript 풀이
- 필수 학습 · 일곱 난쟁이 JavaScript 풀이: 두 명을 제외하는 완전탐색
- 필수 학습 · 코딩테스트 JS Map 풀이: 학급 회장 득표수 세기
- 필수 학습 · 코딩테스트 JS 스택 풀이: 올바른 괄호 검증하기
- 필수 학습 · 코딩테스트 JS 스택 풀이: 괄호문자 제거하기
- 필수 학습 · 코딩테스트 JS 스택 풀이: 크레인 인형뽑기 처리법
- 필수 학습 · 코딩테스트 JS 스택 풀이: 후위식 연산 계산하기
- 필수 학습 · 코딩테스트 JS 스택 풀이: 쇠막대기 레이저 절단 개수 세기
- 필수 학습 · 코딩테스트 JS 투 포인터 풀이: 두 정렬 배열 합치기
- 필수 학습 · 코딩테스트 JS 투 포인터 풀이: 공통 원소 추출하기
- 필수 학습 · 코딩테스트 JS 슬라이딩 윈도우 풀이: 최대 매출 구간 합 계산하기
- 필수 학습 · JavaScript 투 포인터: 합이 M인 연속 부분수열 개수
- 필수 학습 · 코딩테스트 JS 해시 풀이: 모든 아나그램 찾기
- 필수 학습 · 가장 긴 문자열 JavaScript 풀이
- 필수 학습 · 가운데 문자 출력 JavaScript 풀이
- 필수 학습 · 중복문자제거 JavaScript 풀이
- 필수 학습 · 코딩테스트 JS 고급: 최소 힙으로 다익스트라 최단 경로 구하기
- 필수 학습 · 코딩테스트 JS Union-Find: 연결 성분 수와 크기 구하기
- 필수 학습 · 코딩테스트 JS Trie: 접두사에 맞는 단어 수 세기
- 필수 학습 · 코딩테스트 JS Fenwick Tree: 값 갱신과 구간 합 처리
- 필수 학습 · 코딩테스트 JS 세그먼트 트리: 단일 대입과 구간 합 현재 글
- 필수 학습 · 코딩테스트 JS LRU 캐시: 지도 타일 재사용 기록
- 필수 학습 · 코딩테스트 JS AVL 트리: 기준 이상 최솟값 찾기
- 필수 학습 · 코딩테스트 JS 큐: 상담 창구 대기열 명령 처리
- 필수 학습 · 코딩테스트 JS 연결 리스트: 재생 대기 목록 관리
- 필수 학습 · JavaScript 원형 덱 연습: 최근 기록 창과 되돌리기
- 필수 학습 · JavaScript 해시 테이블 연습: 정규화 문자열 빈도와 등장 순서
- 필수 학습 · JavaScript 트리 순회 연습: 깊이별 노드 묶기
- 필수 학습 · JavaScript BST 연습: 닫힌 구간의 중복 키 보고서
- 필수 학습 · JavaScript 최소 힙 연습: 동률 순서를 지키는 작업 스케줄러
- 필수 학습 · JavaScript 그래프 연습: 연결 구역 크기를 작은 순서로 출력하기
- 필수 학습 · 중복단어제거 JavaScript 풀이
- 필수 학습 · TypeScript 이진 탐색 연습: 숫자 카드 존재 여부 확인
- 필수 학습 · 큰 수 출력하기 JavaScript 풀이
- 필수 학습 · 보이는 학생 JavaScript 풀이
- 필수 학습 · 가위바위보 JavaScript 풀이
- 필수 학습 · 점수계산 JavaScript 풀이
새 글 받아보기
RSS 리더에서 BlogFlow의 새 글을 확인할 수 있습니다.