코딩테스트 JS Fenwick Tree: 값 갱신과 구간 합 처리

2026.09.10·수정 2026.09.13·약 11분·작성: 해비·블로그 소개

[창작 문제] 분산 선반의 보정 장부

처음 0인 선반별 수량 기록에 양수·음수 보정을 적용하고 반열린 구간의 순합을 계산합니다. 입력 값은 최종 값이 아니라 보정량입니다.

펜윅 트리의 개념을 표현한 표지 일러스트
주제를 표현한 개념 표지입니다. 정확한 동작과 값의 변화는 아래 코드와 표에서 확인하세요.

이 문제는 BlogFlow 자료구조 학습용으로 독립 창작했습니다. 외부 문제의 지문이나 테스트를 옮기지 않았습니다.

문제

전시 물품 보정 장부에는 n개 선반의 순증감 기록이 있으며 모두 0으로 시작합니다. add 작업은 한 선반에 보정량을 더합니다. sum 작업은 왼쪽 선반부터 오른쪽 경계 바로 전까지의 기록 합을 요청합니다. 순증감 장부이므로 값이 음수가 되어도 유효합니다. 모든 질의의 답을 순서대로 반환하세요.

관련 구현 설명: JavaScript Fenwick Tree: lowbit로 구간 합과 단일 증가 갱신 구현

입력·출력과 제약

JSON 객체 {n,ops}. 작업은 ["add",index,delta] 또는 ["sum",left,right]입니다. 구간은 [left,right)입니다.

sum 작업의 합계를 순서대로 담은 JSON 배열입니다. 빈 구간의 합은 0입니다.

0 ≤ n ≤ 100,000, 작업 수 0~100,000, 정수 보정량 |delta| ≤ 1,000,000. add는 0 ≤ index < n, sum은 0 ≤ left ≤ right ≤ n입니다. 이 제약에서 전체 보정량 절댓값 합은 10^11 이하라 Number 안전 정수 범위입니다.

입력은 위 계약을 만족한다고 가정합니다. 온라인 채점기는 제공하지 않으며 로컬 Node.js에서 JSON 입력으로 실행합니다.

예시

예시 1 입력:

{"n":4,"ops":[["add",0,3],["add",2,5],["sum",0,3],["add",2,-2],["sum",1,4],["sum",2,2]]}

예시 1 출력:

[8,3,0]

예시 2 입력:

{"n":0,"ops":[["sum",0,0]]}

예시 2 출력:

[0]

힌트

[left,right)의 합은 [0,right)의 합에서 [0,left)의 합을 빼면 됩니다. add의 인덱스만 내부에서 1을 더하세요.

정답과 해설

정답 코드와 해설 펼치기

Node.js 24 CommonJS용 전체 풀이입니다. fenwick-problem.cjs로 저장하세요. 클래스 선언, solve 함수, 표준 입력 처리가 모두 들어 있어 가이드 파일을 별도로 가져오지 않습니다. 예시 입력을 input.json에 저장하고 터미널에서 실행합니다.

Windows PowerShell:
Get-Content -Raw -Encoding UTF8 .input.json | node .fenwick-problem.cjs

macOS / Linux:
node fenwick-problem.cjs < input.json
class Fenwick {
  constructor(n) {
    if (!Number.isInteger(n) || n < 0 || n > 1_000_000) {
      throw new RangeError('n은 0~1,000,000 정수여야 합니다.');
    }
    this.n = n;
    this.tree = Array(n + 1).fill(0);
  }
  boundary(i) {
    if (!Number.isInteger(i) || i < 0 || i > this.n) {
      throw new RangeError('경계는 0~n 정수여야 합니다.');
    }
  }
  add(index, delta) {
    this.boundary(index);
    if (index === this.n) throw new RangeError('갱신 인덱스는 n보다 작아야 합니다.');
    if (!Number.isSafeInteger(delta)) throw new TypeError('증가량은 안전한 정수여야 합니다.');
    for (let i = index + 1; i <= this.n; i += i & -i) {
      this.tree[i] += delta;
    }
  }
  prefix(end) {
    this.boundary(end);
    let sum = 0;
    for (let i = end; i > 0; i -= i & -i) sum += this.tree[i];
    return sum;
  }
  sum(left, right) {
    this.boundary(left);
    this.boundary(right);
    if (left > right) throw new RangeError('left는 right 이하여야 합니다.');
    return this.prefix(right) - this.prefix(left);
  }
}

function solve({ n, ops }) {
  const f = new Fenwick(n), out = [];
  for (const [type, a, b] of ops) {
    if (type === 'add') f.add(a, b);
    else if (type === 'sum') out.push(f.sum(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)));
}

처음 두 add 뒤 장부는 [3,0,5,0]입니다. [0,3)의 합은 8입니다. 2번 선반에 −2를 더하면 [3,0,3,0]이 되고 [1,4)의 합은 3입니다. [2,2)는 어떤 원소도 포함하지 않습니다. solve의 분기에서 add는 출력하지 않고 sum만 out에 추가하는 것이 출력 개수를 맞추는 핵심입니다.

내부 인덱스 i는 외부 index+1입니다. lowbit(i)=i & -i는 가장 낮은 1비트의 값입니다. tree[i]는 내부 [i−lowbit(i)+1,i]의 합을 저장합니다. i=6이면 lowbit=2이므로 내부 5,6번 두 원소의 합을 보관합니다. 같은 구간을 무작정 겹쳐 더하는 것이 아니라 질의 때 서로 겹치지 않게 선택합니다.

prefix(end)는 내부 i=end에서 시작합니다. tree[i]를 더한 뒤 i−=lowbit(i)로 방금 더한 블록을 건너뛰면 아직 더하지 않은 앞부분만 남습니다. 끝의 1비트를 하나씩 없애므로 O(log n)개 블록 뒤 0이 됩니다.

초기 배열 O(n), q개 작업 최악 O(q log(n+1)), 구조 O(n+1)와 결과 O(q) 공간입니다. 입력 JSON과 ops 자체도 O(q) 공간을 차지하며 스트리밍 입력 구현은 아닙니다.

표본과 경계를 직접 확인하려면 다음 코드를 fenwick-check.cjs로 저장하고 같은 폴더에서 node fenwick-check.cjs를 실행하세요. assert가 맞으면 출력 없이 종료하고, 다르면 예외와 함께 실패합니다.

const assert = require('node:assert/strict');
const { solve } = require('./fenwick-problem.cjs');

assert.deepEqual(
  solve({
  "n": 4,
  "ops": [
    [
      "add",
      0,
      3
    ],
    [
      "add",
      2,
      5
    ],
    [
      "sum",
      0,
      3
    ],
    [
      "add",
      2,
      -2
    ],
    [
      "sum",
      1,
      4
    ],
    [
      "sum",
      2,
      2
    ]
  ]
}),
  [8,3,0],
);
assert.deepEqual(
  solve({
  "n": 0,
  "ops": [
    [
      "sum",
      0,
      0
    ]
  ]
}),
  [0],
);

연결 학습과 공식 자료

JavaScript Fenwick Tree: lowbit로 구간 합과 단일 증가 갱신 구현에서 연산별 이유와 전체 복잡도를 이어서 확인할 수 있습니다. 다른 형식의 공식 연습은 AtCoder B – Fenwick Tree를 참고하세요. 외부 문제의 정답이나 지문은 이 페이지에 복제하지 않았습니다.

공식 자료 확인일: 2026년 9월 10일. 구현은 이 글의 JavaScript 계약에 맞춰 독립적으로 작성했습니다. 다른 언어 라이브러리의 API와 숫자 범위가 그대로 적용되는 것은 아닙니다.

풀이 전에 확인할 순서

  1. 입력값과 출력값을 한 문장으로 다시 적습니다.
  2. 반복할 대상과 비교·저장할 값을 정합니다.
  3. 필요한 자료구조와 시간복잡도를 예상합니다.
  4. 코드를 보기 전에 손으로 작은 예제를 계산합니다.
힌트: 코딩테스트 JS Fenwick Tree: 값 갱신과 구간 합 처리

정답 코드를 바로 따라 쓰기보다, 본문에서 값이 갱신되는 조건과 반복 범위를 먼저 찾으세요. 반복 한 번마다 반드시 유지되어야 하는 값이 무엇인지 적으면 풀이의 중심 변수를 고르기 쉽습니다.

테스트 확인

  • 가능한 가장 작은 입력
  • 같은 값이나 문자가 반복되는 입력
  • 정답이 처음 또는 마지막 위치에서 결정되는 입력
  • 입력 제한에 가까운 경우의 실행 시간

확인 결과: 본문의 예제뿐 아니라 위 경계 사례에서도 예상값과 실제 출력이 같아야 풀이가 완료됩니다.

이 글이 도움이 되었나요?

조회 중

코딩테스트 JavaScript 학습 순서

필수 49개 · 전체 49개

읽음 기록 관리

전체 과정 목차 (49개)
  1. 필수 길잡이 · 코딩테스트 JS 자료구조 로드맵: 배열, 해시, 스택, 투 포인터 순서
  2. 필수 학습 · 세 수 중 최솟값 JavaScript 조건문 풀이 정리
  3. 필수 학습 · 삼각형 판별하기 JavaScript 풀이
  4. 필수 학습 · 연필 개수 JavaScript 풀이
  5. 필수 학습 · 1부터 N까지 합 출력하기 JavaScript 풀이
  6. 필수 학습 · 최솟값 구하기 JavaScript 풀이|배열 순회와 비교 갱신 원리
  7. 필수 학습 · 홀수 JavaScript 풀이: 조건 판별과 결과 처리 정리
  8. 필수 학습 · 10부제 JavaScript 풀이: 끝자리 비교로 위반 차량 수 세기
  9. 필수 학습 · A를 #으로 JavaScript 풀이: 문자열 순회와 치환
  10. 필수 학습 · 문자 찾기 JavaScript 풀이: 문자열 순회로 개수 세기
  11. 필수 학습 · 대문자 찾기 JavaScript 풀이
  12. 필수 학습 · 대문자로 통일 JavaScript 풀이
  13. 필수 학습 · 대소문자 변환 JavaScript 풀이
  14. 필수 학습 · 일곱 난쟁이 JavaScript 풀이: 두 명을 제외하는 완전탐색
  15. 필수 학습 · 코딩테스트 JS Map 풀이: 학급 회장 득표수 세기
  16. 필수 학습 · 코딩테스트 JS 스택 풀이: 올바른 괄호 검증하기
  17. 필수 학습 · 코딩테스트 JS 스택 풀이: 괄호문자 제거하기
  18. 필수 학습 · 코딩테스트 JS 스택 풀이: 크레인 인형뽑기 처리법
  19. 필수 학습 · 코딩테스트 JS 스택 풀이: 후위식 연산 계산하기
  20. 필수 학습 · 코딩테스트 JS 스택 풀이: 쇠막대기 레이저 절단 개수 세기
  21. 필수 학습 · 코딩테스트 JS 투 포인터 풀이: 두 정렬 배열 합치기
  22. 필수 학습 · 코딩테스트 JS 투 포인터 풀이: 공통 원소 추출하기
  23. 필수 학습 · 코딩테스트 JS 슬라이딩 윈도우 풀이: 최대 매출 구간 합 계산하기
  24. 필수 학습 · JavaScript 투 포인터: 합이 M인 연속 부분수열 개수
  25. 필수 학습 · 코딩테스트 JS 해시 풀이: 모든 아나그램 찾기
  26. 필수 학습 · 가장 긴 문자열 JavaScript 풀이
  27. 필수 학습 · 가운데 문자 출력 JavaScript 풀이
  28. 필수 학습 · 중복문자제거 JavaScript 풀이
  29. 필수 학습 · 코딩테스트 JS 고급: 최소 힙으로 다익스트라 최단 경로 구하기
  30. 필수 학습 · 코딩테스트 JS Union-Find: 연결 성분 수와 크기 구하기
  31. 필수 학습 · 코딩테스트 JS Trie: 접두사에 맞는 단어 수 세기
  32. 필수 학습 · 코딩테스트 JS Fenwick Tree: 값 갱신과 구간 합 처리 현재 글
  33. 필수 학습 · 코딩테스트 JS 세그먼트 트리: 단일 대입과 구간 합
  34. 필수 학습 · 코딩테스트 JS LRU 캐시: 지도 타일 재사용 기록
  35. 필수 학습 · 코딩테스트 JS AVL 트리: 기준 이상 최솟값 찾기
  36. 필수 학습 · 코딩테스트 JS 큐: 상담 창구 대기열 명령 처리
  37. 필수 학습 · 코딩테스트 JS 연결 리스트: 재생 대기 목록 관리
  38. 필수 학습 · JavaScript 원형 덱 연습: 최근 기록 창과 되돌리기
  39. 필수 학습 · JavaScript 해시 테이블 연습: 정규화 문자열 빈도와 등장 순서
  40. 필수 학습 · JavaScript 트리 순회 연습: 깊이별 노드 묶기
  41. 필수 학습 · JavaScript BST 연습: 닫힌 구간의 중복 키 보고서
  42. 필수 학습 · JavaScript 최소 힙 연습: 동률 순서를 지키는 작업 스케줄러
  43. 필수 학습 · JavaScript 그래프 연습: 연결 구역 크기를 작은 순서로 출력하기
  44. 필수 학습 · 중복단어제거 JavaScript 풀이
  45. 필수 학습 · TypeScript 이진 탐색 연습: 숫자 카드 존재 여부 확인
  46. 필수 학습 · 큰 수 출력하기 JavaScript 풀이
  47. 필수 학습 · 보이는 학생 JavaScript 풀이
  48. 필수 학습 · 가위바위보 JavaScript 풀이
  49. 필수 학습 · 점수계산 JavaScript 풀이

새 글 받아보기

RSS 리더에서 BlogFlow의 새 글을 확인할 수 있습니다.

RSS 피드 구독하기

댓글 남기기