알고리즘 유형 트레이너

코딩테스트에서 가장 중요한 건 문제를 읽고 어떤 알고리즘 유형인지 파악하는 능력이다. 객관식으로 유형 감각을 잡고, 주관식으로 스스로 판단하고, 결합유형까지 반복숙달로 마스터하자!
전체 0 정답 0 오답 0 정답률 -

유형 노트

BFS / DFS (너비/깊이 우선 탐색) 모름
그래프/트리를 전체 탐색하거나, 연결 요소를 찾거나, 최단 거리(가중치 없음)를 구할 때
BFS: 큐 사용, 가까운 것부터 → 가중치 1 최단거리 보장
DFS: 스택/재귀, 깊이 우선 → 경로 탐색, 백트래킹에 유리
• "최소 이동 횟수" (가중치 없음) → BFS
• "모든 경로", "경우의 수" → DFS
• "연결 요소 개수", "도달 가능 여부" → 둘 다 가능
• 2차원 격자 탐색 (미로, 섬 개수)
O(V + E)
queue<int> q;
visited[start] = true;
q.push(start);
while (!q.empty()) {
    int u = q.front(); q.pop();
    for (int v : adj[u]) {
        if (!visited[v]) {
            visited[v] = true;
            q.push(v);
        }
    }
}
• BFS 방문 체크를 큐에 넣을 때 안 하고 꺼낼 때 → 중복 방문
• DFS 재귀 깊이 초과 → 스택 DFS 사용
• 격자 BFS 범위 체크 빠뜨림
DP (동적 프로그래밍) 모름
최적 부분 구조(큰 문제의 최적해가 작은 문제의 최적해로 구성) + 중복 부분 문제(같은 하위 문제를 여러 번 풀게 됨)가 동시에 성립할 때
한 번 계산한 결과를 저장(메모이제이션)해서 다시 계산하지 않는다.
점화식을 세우고, 작은 문제부터 채워나가거나(Bottom-Up) 재귀+메모(Top-Down)로 푼다.
• "방법의 수를 구하라"
• "최솟값/최댓값을 구하라" + 선택이 이후에 영향
• "~할 수 있는지 여부" + 상태가 겹침
• 배낭, LCS, LIS, 계단, 동전 교환 등 키워드
1 상태 정의: dp[i]가 뭘 의미하는지 정한다
2 점화식 세우기: dp[i]를 이전 상태로 표현
3 초기값 설정: dp[0], dp[1] 등 베이스 케이스
4 순서 결정: 어떤 방향으로 채울지
5 답 위치 확인: dp[N]인지, max(dp[])인지
보통 O(상태 수 × 전이 비용)
예: 0/1 배낭 → O(N × W), LCS → O(N × M)
int dp[1001];
dp[1] = 1;
dp[2] = 2;
for (int i = 3; i <= N; i++)
    dp[i] = dp[i-1] + dp[i-2];
• 그리디: 현재 최선 선택이 전체 최적해 보장 → 증명 가능
• DP: 현재 선택이 이후에 영향을 줘서 전체를 봐야 함 → 반례 존재 시 DP
• 상태 정의를 모호하게 해서 점화식이 안 세워짐
• 초기값 빠뜨림 → 쓰레기 값으로 전파
• 2차원 DP에서 순회 방향 틀림
그리디 (Greedy) 모름
매 순간 현재 최선의 선택전체 최적해를 보장할 때.
탐욕적 선택 속성 + 최적 부분 구조가 동시에 성립해야 한다.
되돌아가지 않는다. 한번 선택하면 번복 없이 다음으로 넘어간다.
정렬 후 순서대로 처리하는 패턴이 매우 많다.
• "최소 개수로 ~하라" (동전, 구간 커버)
• "최대한 많이 ~하라" (활동 선택, 회의실)
• 정렬 기준이 자연스럽게 보임
• 선택 간 독립성이 느껴짐
sort(meetings.begin(), meetings.end(),
     [](auto& a, auto& b){ return a.second < b.second; });
int cnt = 0, last = 0;
for (auto& [s, e] : meetings) {
    if (s >= last) { cnt++; last = e; }
}
0/1 배낭: 물건 못 쪼갬 → DP
거스름돈 (비배수): [1,3,4]에서 6원 → 그리디 3개, DP 2개
• 반례 하나라도 찾으면 DP로 전환
• 정렬 기준을 잘못 잡음
• 그리디가 최적인지 증명 없이 감으로 적용
• DP 문제를 그리디로 풀어서 반례에 걸림
이분탐색 (Binary Search) 모름
정렬된 배열에서 값을 찾거나, 결정 문제(조건을 만족하는 최소/최대값)를 O(log N)에 풀 때
탐색 범위를 절반씩 줄여나간다. 조건 함수가 단조성(monotonic)을 가지면 이분탐색 가능.
• "정렬된 배열에서 찾아라"
• "최솟값/최댓값을 구하라" + 조건 판별이 O(N)
• "~가 가능한 최대 X" (파라메트릭 서치)
• N이 매우 큼 (10^9 등)
O(log N) — 파라메트릭: O(N log X)
// lower_bound 직접 구현
int lo = 0, hi = n - 1, ans = n;
while (lo <= hi) {
    int mid = (lo + hi) / 2;
    if (arr[mid] >= target) { ans = mid; hi = mid - 1; }
    else lo = mid + 1;
}

// 파라메트릭 서치
long long lo = 1, hi = 1e9, ans = 0;
while (lo <= hi) {
    long long mid = (lo + hi) / 2;
    if (check(mid)) { ans = mid; lo = mid + 1; }
    else hi = mid - 1;
}
• lo, hi 범위 설정 오류 (off-by-one)
• 무한 루프: lo < hi vs lo <= hi 혼동
• 파라메트릭에서 check 함수의 단조성 미확인
투 포인터 (Two Pointer) 모름
정렬된 배열에서 조건을 만족하는 쌍/구간을 찾거나, 연속 구간의 합/길이를 조건에 맞게 탐색할 때
두 개의 포인터(시작/끝 또는 양쪽 끝)를 조건에 따라 이동시켜 O(N)에 해결.
• "합이 X인 쌍" + 정렬됨
• "연속 구간의 합이 S 이상인 최소 길이" (양수만)
• 3Sum, 4Sum 등 k-Sum 문제
• "두 배열에서 조건을 만족하는 쌍"
투 포인터: 두 포인터가 독립적으로 이동, 구간 길이 가변
슬라이딩 윈도우: 고정 크기 윈도우가 한 칸씩 이동
• 양수 배열 연속 합 → 투 포인터 / 고정 길이 K → 슬라이딩 윈도우
O(N) — 각 포인터가 최대 N번 이동
int lo = 0, sum = 0, ans = INT_MAX;
for (int hi = 0; hi < n; hi++) {
    sum += arr[hi];
    while (sum >= S) {
        ans = min(ans, hi - lo + 1);
        sum -= arr[lo++];
    }
}
• 음수가 있으면 투 포인터 불가 (단조성 깨짐) → 누적합+해시
• while 조건 빠뜨려서 포인터가 역전
• 정렬 안 하고 양쪽 끝 투 포인터 적용
슬라이딩 윈도우 (Sliding Window) 모름
고정 크기 K의 연속 구간에서 합/최대/최소/중복 등을 구할 때
윈도우를 한 칸 밀 때마다 나가는 원소 빼고 들어오는 원소 더한다. O(N).
• "길이 K인 연속 부분 배열/문자열"
• "고정 크기 구간의 합/평균/최대"
• "서로 다른 문자가 K개 이하인 가장 긴 부분 문자열"
O(N)
int sum = 0, maxSum;
for (int i = 0; i < K; i++) sum += arr[i];
maxSum = sum;
for (int i = K; i < n; i++) {
    sum += arr[i] - arr[i - K];
    maxSum = max(maxSum, sum);
}
• 윈도우 초기화를 빠뜨림
• 가변 길이 구간에 슬라이딩 윈도우 적용 (→ 투 포인터 사용)
• 나가는 원소 처리 순서 오류
누적합 (Prefix Sum) 모름
값이 변하지 않는 배열에서 구간 합 쿼리를 O(1)에 답할 때
prefix[i] = arr[0] + ... + arr[i-1] 을 미리 계산.
구간 합 = prefix[r+1] - prefix[l]
• "구간 합을 여러 번 구하라" + 값 변경 없음
• "연속 부분 수열 합이 K" → prefix[j]-prefix[i]=K → 해시맵 조합
• 2차원 누적합: 직사각형 영역 합
값 변경 없음 → 누적합 (쿼리 O(1))
값 변경 있음 → 세그먼트 트리 (쿼리 O(log N))
전처리 O(N), 쿼리 O(1)
vector<long long> prefix(n + 1, 0);
for (int i = 0; i < n; i++)
    prefix[i + 1] = prefix[i] + arr[i];
// 구간 [l, r] 합 = prefix[r+1] - prefix[l]
• 인덱스 off-by-one (0-indexed vs 1-indexed)
• 음수 포함 시 투포인터 대신 누적합+해시 사용해야 함을 모름
• 2차원 누적합에서 포함-배제 원리 실수
분할정복 (Divide & Conquer) 모름
문제를 같은 구조의 더 작은 부분 문제로 쪼개고, 각각 풀어서 합치면 전체 답이 될 때
Divide(쪼개기) → Conquer(각각 풀기) → Merge(합치기). 재귀적으로 반복.
• 배열/구간을 반으로 나눠서 처리
• 거듭제곱, 행렬 곱셈 등 크기를 절반으로 줄이는 연산
• 가장 가까운 점 쌍 (Closest Pair)
마스터 정리: T(n) = aT(n/b) + O(n^d)
머지소트: O(N log N), 거듭제곱: O(log N)
long long power(long long a, long long n, long long mod) {
    if (n == 0) return 1;
    long long half = power(a, n / 2, mod);
    half = half * half % mod;
    if (n % 2) half = half * a % mod;
    return half;
}
분할정복: 부분 문제가 서로 겹치지 않음
DP: 부분 문제가 겹침(중복) → 저장해서 재사용
• base case를 빠뜨려서 무한 재귀
• merge 단계의 복잡도를 간과해서 시간초과
퀵 셀렉트 (Quick Select) 모름
배열에서 K번째로 작은/큰 원소를 전체 정렬 없이 빠르게 구할 때
퀵소트처럼 피벗 분할 후, K번째가 있는 한쪽만 재귀.
평균 O(N), 최악 O(N²)
// STL nth_element가 퀵 셀렉트 구현
nth_element(arr.begin(), arr.begin() + k, arr.end());
// arr[k]가 k번째로 작은 원소로 확정됨
• K가 0-indexed인지 1-indexed인지 헷갈림
• 최악 O(N²) 방지를 위한 랜덤 피벗 안 씀
스택 (Stack) 모름
LIFO(후입선출) 구조가 필요할 때: 괄호 매칭, 히스토그램, 단조 스택 등
• "올바른 괄호" 판별/생성
• "가장 가까운 큰/작은 수" (NGE, 단조 스택)
• "히스토그램 최대 직사각형"
• 후위 표기식 계산, 재귀 시뮬레이션
스택 (LIFO): 가장 최근 것부터 처리 — 괄호, 뒤로가기
(FIFO): 먼저 온 것부터 처리 — BFS, 대기열
push/pop/top: O(1)
stack<char> st;
for (char c : s) {
    if (c == '(') st.push(c);
    else {
        if (st.empty()) { cout << "NO"; return; }
        st.pop();
    }
}
cout << (st.empty() ? "YES" : "NO");
• empty() 체크 없이 top()/pop() → 런타임 에러
• 단조 스택에서 같은 값(=) 처리 기준 혼동
큐 / 덱 (Queue / Deque) 모름
큐(FIFO): BFS, 프로세스 스케줄링, 순서 보존 처리
: 양쪽 끝 삽입/삭제, 슬라이딩 윈도우 최솟값/최댓값
• BFS 탐색
• "슬라이딩 윈도우 구간 최솟값/최댓값" → 단조 덱
• 0-1 BFS (가중치 0 또는 1) → 덱 사용
• 조세푸스 문제
push/pop (양쪽): O(1)
deque<int> dq;  // 인덱스 저장
for (int i = 0; i < n; i++) {
    while (!dq.empty() && dq.front() <= i - K) dq.pop_front();
    while (!dq.empty() && arr[dq.back()] >= arr[i]) dq.pop_back();
    dq.push_back(i);
    if (i >= K - 1) cout << arr[dq.front()] << ' ';
}
• 덱에 값이 아닌 인덱스를 저장해야 윈도우 범위 체크 가능
• 0-1 BFS에서 가중치 0은 앞에, 1은 뒤에 넣어야 함
해시 (Hash Map / Set) 모름
키-값 O(1) 조회/삽입/삭제가 필요할 때. 빈도 카운팅, 중복 제거, 두 수의 합 등.
• "합이 X인 쌍이 존재하는가" (정렬 안 되어 있을 때)
• "빈도수/등장 횟수를 구하라"
• "중복 제거"
• 누적합 + 해시맵 (연속 구간 합 = K)
평균 O(1), 최악 O(N) — unordered_map
unordered_map<int, int> seen;
for (int i = 0; i < n; i++) {
    int need = target - arr[i];
    if (seen.count(need)) {
        cout << seen[need] << " " << i;
        return;
    }
    seen[arr[i]] = i;
}
• unordered_map 해시 충돌로 TLE → 커스텀 해시 또는 map 사용
• 빈 키 접근 시 자동 삽입됨 (operator[] 특성)
백트래킹 (Backtracking) 모름
가능한 모든 경우를 체계적으로 탐색하되, 유망하지 않은 가지를 가지치기해서 효율을 높일 때
DFS로 탐색하면서, 조건에 맞지 않으면 즉시 되돌아간다 (pruning).
• "모든 경우의 수를 구하라" + N이 작음 (≤ 20)
• 순열, 조합, 부분집합 생성
• N-Queen, 스도쿠 등 제약 만족 문제
• "조건을 만족하는 모든 해를 출력하라"
최악 O(N!) 또는 O(2^N) — 가지치기로 실제로는 훨씬 빠름
bool used[MAX];
vector<int> perm;

void solve(int depth) {
    if (depth == N) { /* perm 출력 */ return; }
    for (int i = 1; i <= N; i++) {
        if (used[i]) continue;
        used[i] = true;
        perm.push_back(i);
        solve(depth + 1);
        perm.pop_back();
        used[i] = false;
    }
}
• 가지치기 조건을 안 넣어서 시간초과
• 상태 복원(되돌리기)을 빠뜨림
• N이 크면 백트래킹 불가 → DP나 그리디로 전환
비트마스킹 (Bitmask) 모름
부분집합을 정수 하나로 표현하고, 집합 연산을 비트 연산으로 O(1)에 처리할 때
N개 원소의 부분집합을 0 ~ 2^N-1 정수로 표현. i번째 비트 = i번째 원소 포함 여부.
• N이 매우 작음 (≤ 20)
• "모든 부분집합" 또는 "방문 상태"를 추적
• TSP (외판원 문제): dp[방문상태][현재위치]
• DP 상태를 집합으로 표현해야 할 때
• i번째 켜기: mask | (1 << i)
• i번째 끄기: mask & ~(1 << i)
• i번째 확인: mask & (1 << i)
• 부분집합 순회: for(int s=mask; s; s=(s-1)&mask)
부분집합 전체 순회: O(2^N)
비트마스크 DP: O(2^N × N) 또는 O(2^N × N²)
// dp[mask][i] = mask 상태에서 i에 도착하는 최소 비용
dp[1][0] = 0;
for (int mask = 1; mask < (1 << N); mask++)
    for (int u = 0; u < N; u++) {
        if (!(mask & (1 << u))) continue;
        for (int v = 0; v < N; v++) {
            if (mask & (1 << v)) continue;
            int next = mask | (1 << v);
            dp[next][v] = min(dp[next][v], dp[mask][u] + dist[u][v]);
        }
    }
• N > 20이면 2^N이 너무 커서 불가
• 1 << i에서 int 오버플로우 → 1LL << i 사용
• 부분집합 순회에서 공집합(0) 처리 빠뜨림
다익스트라 (Dijkstra) 모름
가중치가 양수인 그래프에서 하나의 출발점으로부터 다른 모든 정점까지의 최단 거리를 구할 때
"지금까지 발견한 최단 거리가 가장 짧은 정점부터 확정한다."
확정된 정점에서 뻗어나가 인접 정점의 거리를 갱신(relaxation)하고, 다시 가장 짧은 걸 꺼내서 반복.
1 출발점 거리 = 0, 나머지 = ∞ 로 초기화
2 우선순위 큐(min-heap)에 (0, 출발점) 삽입
3 큐에서 거리가 가장 짧은 정점 u를 꺼냄
4 u의 인접 정점 v에 대해: dist[u] + w(u,v) < dist[v] 이면 갱신, 큐에 삽입
5 큐가 빌 때까지 3~4 반복
우선순위 큐: O((V + E) log V)
priority_queue<pii, vector<pii>, greater<pii>> pq;
dist[start] = 0;
pq.push({0, start});
while (!pq.empty()) {
    auto [d, u] = pq.top(); pq.pop();
    if (d > dist[u]) continue;
    for (auto [v, w] : adj[u]) {
        if (dist[u] + w < dist[v]) {
            dist[v] = dist[u] + w;
            pq.push({dist[v], v});
        }
    }
}
다익스트라벨만-포드BFS플로이드
음수 가중치
출발점단일단일단일모든 쌍
복잡도O((V+E)logV)O(VE)O(V+E)O(V³)
• 음수 가중치에 다익스트라 쓰면 틀림
if (d > dist[u]) continue; 빼먹으면 TLE
• 인접 행렬 쓰면 N 크면 MLE
벨만-포드 (Bellman-Ford) 모름
음수 가중치가 있는 그래프에서 단일 출발점 최단 거리를 구할 때. 음수 사이클 판별도 가능.
모든 간선에 대해 relaxation을 V-1번 반복. V번째에도 갱신 → 음수 사이클.
양수만 → 다익스트라 (빠름)
음수 존재 → 벨만-포드
음수 사이클 판별 → 벨만-포드만 가능
O(V × E)
vector<long long> dist(N+1, INF);
dist[start] = 0;
for (int i = 0; i < N-1; i++)
    for (auto& [u, v, w] : edges)
        if (dist[u] != INF && dist[u] + w < dist[v])
            dist[v] = dist[u] + w;
// 음수 사이클: 한 번 더 돌려서 갱신되면 존재
• dist[u]가 INF일 때도 갱신 → 오버플로우
• V-1번이 아니라 E-1번 돌리는 실수
플로이드-워셜 (Floyd-Warshall) 모름
모든 정점 쌍 간의 최단 거리를 구할 때. N이 작음 (≤ 500).
"정점 k를 경유하면 더 짧아지는가?"를 모든 (i, j) 쌍에 대해 확인.
dist[i][j] = min(dist[i][j], dist[i][k] + dist[k][j])
O(N³)
for (int k = 1; k <= N; k++)
    for (int i = 1; i <= N; i++)
        for (int j = 1; j <= N; j++)
            dist[i][j] = min(dist[i][j], dist[i][k] + dist[k][j]);
k가 가장 바깥 루프여야 함
• dist[i][i] = 0 초기화 빠뜨림
• INF + INF 오버플로우
위상정렬 (Topological Sort) 모름
방향 비순환 그래프(DAG)에서 선후관계를 지키는 순서를 구할 때
진입차수 0인 노드부터 처리 → 연결 노드 진입차수 -1 → 새로 0이면 큐에 추가.
• "A를 먼저 해야 B를 할 수 있다" (선수과목, 작업 순서)
• "순서대로 나열하라" + 일부 쌍의 선후관계만 주어짐
• 빌드 순서, 의존성 해결
O(V + E)
queue<int> q;
for (int i = 1; i <= N; i++)
    if (indeg[i] == 0) q.push(i);
while (!q.empty()) {
    int u = q.front(); q.pop();
    result.push_back(u);
    for (int v : adj[u])
        if (--indeg[v] == 0) q.push(v);
}
// result.size() != N → 사이클 존재
• 사이클 검출 안 해서 무한루프
• "사전순 최소" 요구 시 큐 대신 우선순위 큐
최소 스패닝 트리 (MST) 모름
모든 정점을 연결하되 간선 비용 합이 최소인 트리를 구할 때
Kruskal: 간선 비용순 정렬 → 사이클 안 생기면 추가 (유니온파인드)
Prim: 임의 정점에서 시작 → 가장 싼 간선 추가
MST: 모든 점 연결, 트리 비용 합 최소
다익스트라: 한 점→다른 점, 경로 비용 최소
• "전부 이어라" → MST / "A에서 B까지" → 다익스트라
Kruskal: O(E log E)
sort(edges.begin(), edges.end(), [](auto& a, auto& b){ return a.w < b.w; });
int total = 0, cnt = 0;
for (auto& [u, v, w] : edges) {
    if (unite(u, v)) {
        total += w;
        if (++cnt == N - 1) break;
    }
}
• 유니온파인드 경로 압축 빠뜨려서 TLE
• N-1개 간선이면 즉시 종료해야 하는데 끝까지 돌림
유니온파인드 (Union-Find) 모름
원소들을 그룹으로 합치고, 두 원소가 같은 그룹인지 판별하는 연산이 반복될 때
각 그룹을 트리로 표현. Find: 루트를 찾는다 (경로 압축). Union: 두 그룹 합침.
그래프 고정 + 연결 요소 → BFS/DFS
관계가 계속 추가 + 같은 그룹 질의 → 유니온파인드
Find, Union: O(α(N)) ≈ O(1)
int parent[MAX], rnk[MAX];
void init(int n) { for (int i = 0; i <= n; i++) parent[i] = i, rnk[i] = 0; }
int find(int x) { return parent[x] == x ? x : parent[x] = find(parent[x]); }
bool unite(int a, int b) {
    a = find(a); b = find(b);
    if (a == b) return false;
    if (rnk[a] < rnk[b]) swap(a, b);
    parent[b] = a;
    if (rnk[a] == rnk[b]) rnk[a]++;
    return true;
}
• 경로 압축 안 하면 최악 O(N)으로 TLE
• init() 빠뜨림
• unite 반환값 체크 안 함 (MST에서 중요)
KMP 알고리즘 모름
긴 문자열 S에서 패턴 P가 등장하는 모든 위치를 빠르게 찾을 때
패턴의 실패 함수를 미리 계산. 불일치 시 처음부터 비교하지 않고 건너뜀.
전처리 O(M), 탐색 O(N) → 총 O(N + M)
vector<int> getPi(string& p) {
    int m = p.size();
    vector<int> pi(m, 0);
    int j = 0;
    for (int i = 1; i < m; i++) {
        while (j > 0 && p[i] != p[j]) j = pi[j-1];
        if (p[i] == p[j]) pi[i] = ++j;
    }
    return pi;
}
• 실패 함수에서 while을 if로 써서 틀림
• 매칭 성공 후 j = pi[j-1] 안 해서 다음 매칭 놓침
트라이 (Trie) 모름
문자열 집합에서 접두사 검색, 자동완성, 사전순 정렬 등을 빠르게 할 때
각 노드가 한 글자를 나타내는 트리. 루트→리프 경로가 하나의 문자열.
• "접두사가 같은 문자열 개수"
• "사전에서 빠르게 검색"
• "XOR 최댓값" (비트 트라이)
• 전화번호 일관성 검사
삽입/검색: O(L) (L = 문자열 길이)
struct Trie {
    int children[26] = {};
    bool isEnd = false;
} nodes[MAX_NODES];
int cnt = 1;

void insert(const string& s) {
    int cur = 0;
    for (char c : s) {
        int idx = c - 'a';
        if (!nodes[cur].children[idx])
            nodes[cur].children[idx] = cnt++;
        cur = nodes[cur].children[idx];
    }
    nodes[cur].isEnd = true;
}
• 메모리 초과: 노드 수 = 문자열 총 길이까지 가능
• isEnd 체크 안 해서 접두사만 매칭됨
세그먼트 트리 (Segment Tree) 모름
배열의 구간 쿼리(합, 최소, 최대)와 값 업데이트가 반복될 때. 둘 다 O(log N).
값 변경 없음 + 구간 합 → 누적합 O(1)
값 변경 있음 + 구간 쿼리 → 세그먼트 트리 O(log N)
값 변경 없음 + 구간 min/max → 희소 테이블 O(1)
빌드 O(N), 쿼리/업데이트 O(log N)
int tree[4 * MAX];
void build(int node, int s, int e, int arr[]) {
    if (s == e) { tree[node] = arr[s]; return; }
    int mid = (s + e) / 2;
    build(2*node, s, mid, arr);
    build(2*node+1, mid+1, e, arr);
    tree[node] = tree[2*node] + tree[2*node+1];
}
int query(int node, int s, int e, int l, int r) {
    if (r < s || e < l) return 0;
    if (l <= s && e <= r) return tree[node];
    int mid = (s + e) / 2;
    return query(2*node, s, mid, l, r) + query(2*node+1, mid+1, e, l, r);
}
• 트리 크기 4*N 안 잡아서 런타임 에러
• 구간 범위 체크 잘못
• Lazy Propagation 필요한데 안 써서 TLE
희소 테이블 (Sparse Table) 모름
정적 배열에서 구간 최소/최대 쿼리를 O(1)에. LCA에서 2^k번째 조상 전처리에도 사용.
table[k][i] = i번째부터 길이 2^k 구간의 min/max를 미리 계산.
값 변경 없음 + min/max → 희소 테이블 (쿼리 O(1))
값 변경 있음 → 세그먼트 트리
• 구간 합에는 사용 불가 (겹치면 합 중복)
전처리 O(N log N), 쿼리 O(1)
int sp[LOG][MAX];
void build(int arr[], int n) {
    for (int i = 0; i < n; i++) sp[0][i] = arr[i];
    for (int k = 1; (1 << k) <= n; k++)
        for (int i = 0; i + (1 << k) <= n; i++)
            sp[k][i] = min(sp[k-1][i], sp[k-1][i + (1 << (k-1))]);
}
int query(int l, int r) {
    int k = __lg(r - l + 1);
    return min(sp[k][l], sp[k][r - (1 << k) + 1]);
}
• LOG 크기 부족 → 배열 범위 초과
• 구간 합에 적용 (겹침 → 중복 카운팅)
수학 / 정수론 모름
에라토스테네스 체: N 이하 소수 O(N log log N)
유클리드 GCD: gcd(a,b) = gcd(b, a%b) O(log N)
확장 유클리드: ax + by = gcd(a,b) 해 구하기
모듈러 역원: a^(p-2) mod p (페르마 소정리)
조합론: nCr = n! / (r! × (n-r)!) mod p
• "소수 판별", "소수의 개수"
• "최대공약수/최소공배수"
• "nCr mod p"
• "~의 나머지를 구하라" (모듈러 연산)
vector<bool> is_prime(N+1, true);
is_prime[0] = is_prime[1] = false;
for (int i = 2; i * i <= N; i++)
    if (is_prime[i])
        for (int j = i*i; j <= N; j += i)
            is_prime[j] = false;
• 모듈러 연산에서 뺄셈 시 음수 → (a - b % MOD + MOD) % MOD
• 오버플로우: (a * b) % MOD에서 a*b가 long long 범위 초과
• nCr에서 역원 계산을 안 하고 나눗셈 직접 수행
좌표 압축 (Coordinate Compression) 모름
값의 범위는 매우 크지만 (10^9) 실제 사용되는 값의 종류는 적을 때 (N ≤ 10^5). 인덱스로 쓸 수 있도록 압축.
정렬 → 중복 제거 → lower_bound로 순위 매기기. 값의 상대적 순서만 보존.
• 값이 10^9 이상인데 배열 인덱스로 사용해야 함
• 세그먼트 트리 + 값 범위가 큼 → 압축 후 세그트리
• "순위를 구하라"
O(N log N) (정렬 지배)
vector<int> sorted_vals(arr, arr + n);
sort(sorted_vals.begin(), sorted_vals.end());
sorted_vals.erase(unique(sorted_vals.begin(), sorted_vals.end()), sorted_vals.end());

for (int i = 0; i < n; i++)
    arr[i] = lower_bound(sorted_vals.begin(), sorted_vals.end(), arr[i])
             - sorted_vals.begin();
• unique 전에 정렬 안 함
• 압축 후 원래 값이 필요할 때 역매핑 안 만듦

문제 기록

아직 풀어본 문제가 없습니다.
문제 풀기