Деревья отрезков и запросы на диапазонах на С++ на...
Created on: June 17, 2025
Created on: June 17, 2025
Деревья отрезков и запросы на диапазонах на С++ на русском
Ниже — компактный «конспект» по деревьям отрезков (segment tree) на C++ с примерами кода и пояснениями на русском языке.
Дерево отрезков — это сбалансированная структура данных поверх массива, позволяющая:
O(n)
.O(log n)
.O(log n)
(с «лэйзи-пропагацией» — когда обновляем сразу целые отрезки).cpp#include <bits/stdc++.h> using namespace std; struct SegTree { int n; vector<long long> t; // 1-based индексирование для простоты explicit SegTree(const vector<long long>& a) { n = (int)a.size(); t.assign(4 * n, 0); build(a, 1, 0, n - 1); } void build(const vector<long long>& a, int v, int l, int r) { if (l == r) { t[v] = a[l]; } else { int m = (l + r) / 2; build(a, v * 2, l, m); build(a, v * 2 + 1, m + 1, r); t[v] = t[v * 2] + t[v * 2 + 1]; // операция: сумма } } // Сумма на [ql, qr] long long query(int v, int l, int r, int ql, int qr) { if (qr < l || r < ql) return 0; // вне диапазона if (ql <= l && r <= qr) return t[v]; // целиком внутри int m = (l + r) / 2; return query(v * 2, l, m, ql, qr) + query(v * 2 + 1, m + 1, r, ql, qr); } long long query(int l, int r) { return query(1, 0, n - 1, l, r); } // Обновление позиции pos ← new_val void update(int v, int l, int r, int pos, long long new_val) { if (l == r) { t[v] = new_val; } else { int m = (l + r) / 2; if (pos <= m) update(v * 2, l, m, pos, new_val); else update(v * 2 + 1, m + 1, r, pos, new_val); t[v] = t[v * 2] + t[v * 2 + 1]; } } void update(int pos, long long new_val) { update(1, 0, n - 1, pos, new_val); } };
Операция | Время | Память |
---|---|---|
Построение | O(n) | ≈ 4 n |
Запрос диапазона | O(log n) | |
Обновление точки | O(log n) |
Часто нужен «прибавить +x
на всем [l, r]» или «задать значение на всем отрезке».
Добавим массив lazy
:
cppstruct SegTreeLazy { int n; vector<long long> t, add; // add хранит «отложенное» прибавление explicit SegTreeLazy(const vector<long long>& a) { n = (int)a.size(); t.assign(4 * n, 0); add.assign(4 * n, 0); build(a, 1, 0, n - 1); } /* build как раньше (сумма) */ void push(int v, int l, int r) { if (add[v] == 0 || l == r) return; int m = (l + r) / 2; apply(v * 2, l, m, add[v]); apply(v * 2 + 1, m + 1, r, add[v]); add[v] = 0; } void apply(int v, int l, int r, long long delta) { t[v] += delta * (r - l + 1); // меняем агрегат add[v] += delta; // копим «долг» детям } // Прибавить delta на [ql, qr] void range_add(int v, int l, int r, int ql, int qr, long long delta) { if (qr < l || r < ql) return; if (ql <= l && r <= qr) { apply(v, l, r, delta); return; } push(v, l, r); int m = (l + r) / 2; range_add(v * 2, l, m, ql, qr, delta); range_add(v * 2 + 1, m + 1, r, ql, qr, delta); t[v] = t[v * 2] + t[v * 2 + 1]; } void range_add(int l, int r, long long delta) { range_add(1, 0, n - 1, l, r, delta); } long long query(int v, int l, int r, int ql, int qr) { if (qr < l || r < ql) return 0; if (ql <= l && r <= qr) return t[v]; push(v, l, r); int m = (l + r) / 2; return query(v * 2, l, m, ql, qr) + query(v * 2 + 1, m + 1, r, ql, qr); } long long query(int l, int r) { return query(1, 0, n - 1, l, r); } };
Запрос и обновление — всё ещё O(log n)
; память ≈ 8 n
.
Ситуация | Приём |
---|---|
Только точечные обновления + суммы | Можно заменить Fenwickʼом (BIT); проще, но на некоторые задачи не годится. |
Нужно min/max + добавление на отрезке | Лэйзи-дерево с операциями «прибавить + x» и «взять min» (вектор дополнительно хранит t и add ). |
Комбинация разных функций | Храните в узле структуру (например, сумму и минимум сразу). |
Итеративное дерево | Можно хранить массив размера 2 n , как в «iterative segment tree» (короче код, но лэйзи-пропагация чуть сложнее). |
Сжатие координат | Если индексы до 1e9, сожмите их перед построением. |
cppint main() { ios::sync_with_stdio(false); cin.tie(nullptr); vector<long long> a = {1, 2, 3, 4, 5}; SegTreeLazy st(a); cout << st.query(1, 3) << '\n'; // сумма 2+3+4 = 9 st.range_add(0, 2, 10); // +10 к элементам 0..2 cout << st.query(0, 4) << '\n'; // 11+12+13+4+5 = 45 }
Вывод:
text9 45
O(log n)
.Дерево отрезков — универсальная структура для диапазонных задач.
С база + лэйзи-вариантами покрывает львиную долю задач с O(log n)
-временем и линейной (по n) памятью.
Удачного кодинга!
бех #include <bits/stdc++.h>
Ниже тот же код «классического» дерева отрезков, но без универсального заголовка #include <bits/stdc++.h>
— только нужные стандартные заголовки.
cpp#include <iostream> // cout / cin #include <vector> // std::vector #include <cstdint> // int64_t, если хотите явно #include <algorithm> // std::max, std::min (при желании) using namespace std; /* точечные обновления + запрос суммы */ struct SegTree { int n; vector<int64_t> t; explicit SegTree(const vector<int64_t>& a) { n = static_cast<int>(a.size()); t.assign(4 * n, 0); build(a, 1, 0, n - 1); } void build(const vector<int64_t>& a, int v, int l, int r) { if (l == r) { t[v] = a[l]; } else { int m = (l + r) / 2; build(a, v * 2, l, m); build(a, v * 2 + 1, m + 1, r); t[v] = t[v * 2] + t[v * 2 + 1]; } } int64_t query(int v, int l, int r, int ql, int qr) const { if (qr < l || r < ql) return 0; // вне диапазона if (ql <= l && r <= qr) return t[v]; // полностью внутри int m = (l + r) / 2; return query(v * 2, l, m, ql, qr) + query(v * 2 + 1, m + 1, r, ql, qr); } int64_t query(int l, int r) const { return query(1, 0, n - 1, l, r); } void update(int v, int l, int r, int pos, int64_t val) { if (l == r) { t[v] = val; } else { int m = (l + r) / 2; if (pos <= m) update(v * 2, l, m, pos, val); else update(v * 2 + 1, m + 1, r, pos, val); t[v] = t[v * 2] + t[v * 2 + 1]; } } void update(int pos, int64_t val) { update(1, 0, n - 1, pos, val); } };
Заголовок | Зачем нужен |
---|---|
<iostream> | std::cin , std::cout , std::endl |
<vector> | динамический массив std::vector |
<cstdint> | фиксированные типы (int64_t и др.) |
<algorithm> * | если понадобятся std::min , std::max , std::swap и т.п. |
* В примере он не обязателен, но часто пригождается.
cppint main() { ios::sync_with_stdio(false); cin.tie(nullptr); vector<int64_t> a = {1, 2, 3, 4, 5}; SegTree st(a); cout << st.query(1, 3) << '\n'; // 2+3+4 = 9 st.update(2, 10); // a[2] = 10 cout << st.query(0, 4) << '\n'; // 1+2+10+4+5 = 22 return 0; }
Скомпилируется, например:
bashg++ -std=c++17 -O2 segtree.cpp -o segtree
Если нужен вариант с лэйзи-пропагацией для обновления целого диапазона, просто добавьте массив
add
, функциюpush
и заменитеupdate
↔range_add
, как показывалось ранее — заголовки остаются те же.
Серьезность Код Описание Проект Файл Строка Состояние подавления Подробности
Ошибка LNK2019 ссылка на неразрешенный внешний символ main в функции "int __cdecl invoke_main(void)" (?invoke_main@@YAHXZ). Project6 C:\Users\user\source\repos\Project6\Project6\MSVCRTD.lib(exe_main.obj) 1
Ошибка LNK2019: unresolved external symbol main означает, что линковщик MSVC собрал все объектные файлы, но так и не нашёл функцию main
(или wmain
/ WinMain
) — точку входа программы. Ниже кратко перечислены самые частые причины и способы исправления.
Причина | Как проверить / исправить |
---|---|
1. В коде действительно нет main | Добавьте в один из .cpp файлов явно объявленную функцию: <br>int main() { /* … */ return 0; } |
2. Опечатка или нестандартная сигнатура | Имя написано как Main , main() в классе, или у main не-int возврат. Должно быть ровно int main() или int main(int argc, char* argv[]) . |
3. Подсистема приложения не совпадает с точкой входа | Console-проекты ищут main / wmain , а Windows-проекты — WinMain / wWinMain . <br>- Откройте Project → Properties → Linker → System → Subsystem и убедитесь, что там Console, если у вас main . |
4. Файл с main не добавлен в проект | В дереве Solution Explorer убедитесь, что ваш .cpp отмечен и компилируется (нет ли значка ➖). Если он лежит в папке, но не «Add → Existing Item», линкер его не увидит. |
5. Несовместимость с PCH / Unity build | Если включены предкомпилированные заголовки и вы создали «Empty Project», убедитесь, что к файлу с main добавлена директива #include "pch.h" (если PCH требуется) или отключите PCH для файла. |
6. Вы создали Static Library / DLL, а не EXE | Проверьте Configuration Type (Project → Properties → General). Для исполняемого файла должно быть Application (.exe). |
main
и убедитесь, что он компилируется:
cpp#include <iostream> int main() { std::cout << "OK\n"; return 0; }
После устранения несоответствия линковщик найдёт точку входа, и LNK2019 исчезнет.