📐 Правоъгълници и Координатна Геометрия
Задачите с правоъгълници, чиито страни са успоредни на координатните оси (Axis-Aligned Bounding Box - AABB), са изключително често срещани. Те са основата за по-сложни алгоритми като Sweep-Line и Segment Trees.
1. Представяне
Най-удобният начин за съхранение е чрез координатите на долния ляв \((x_1, y_1)\) и горния десен \((x_2, y_2)\) връх. Важно: Уверете се, че \(x_1 \le x_2\) и \(y_1 \le y_2\).
struct Rect {
long long x1, y1, x2, y2; // Използвайте long long за да избегнете overflow при площ
long long width() const { return max(0LL, x2 - x1); }
long long height() const { return max(0LL, y2 - y1); }
long long area() const { return width() * height(); }
};
2. Основни операции
2.1. Сечение (Intersection)
Сечението на два правоъгълника \(A\) и \(B\) също е правоъгълник (или празно множество).
Координатите му се получават чрез вземане на "вътрешните" граници: max от левите/долните и min от десните/горните.
Rect intersect(const Rect& a, const Rect& b) {
Rect res;
res.x1 = max(a.x1, b.x1);
res.y1 = max(a.y1, b.y1);
res.x2 = min(a.x2, b.x2);
res.y2 = min(a.y2, b.y2);
// Ако е невалиден (с отрицателни страни), връщаме нулев
if (res.x1 >= res.x2 || res.y1 >= res.y2)
return {0, 0, 0, 0};
return res;
}
2.2. Обединение (Union Area)
За разлика от сечението, обединението на правоъгълници не е непременно правоъгълник. Обикновено се търси площта на обединението.
За 2 правоъгълника (Принцип на включване и изключване): \(S(A \cup B) = S(A) + S(B) - S(A \cap B)\)
За 3 правоъгълника: \(S(A \cup B \cup C) = S(A) + S(B) + S(C) - S(A \cap B) - S(A \cap C) - S(B \cap C) + S(A \cap B \cap C)\)
За \(N\) правоъгълника се използва методът на Замитащата права (Sweep Line), който е разгледан в Тема 27.
2.3. Точка в правоъгълник
bool contains(const Rect& r, long long x, long long y) {
return x >= r.x1 && x <= r.x2 && y >= r.y1 && y <= r.y2;
// Внимавайте със строгите неравенства (< vs <=) според условието
}
3. 2D Префиксни Суми (2D Prefix Sums)
Ако имаме матрица \(A\) с числа и множество заявки за сумата в подправоъгълник \((x_1, y_1)\) до \((x_2, y_2)\), можем да отговаряме за \(O(1)\) след препроцесинг \(O(N \cdot M)\).
Препроцесинг
Създаваме матрица pref, където pref[i][j] пази сумата на правоъгълника от \((1, 1)\) до \((i, j)\).
Формула (включване-изключване):
pref[i][j] = matrix[i][j] + pref[i-1][j] + pref[i][j-1] - pref[i-1][j-1]
Заявка
Сумата в правоъгълник, дефиниран от \((r1, c1)\) (горе-ляво) и \((r2, c2)\) (долу-дясно):
Sum = pref[r2][c2] - pref[r1-1][c2] - pref[r2][c1-1] + pref[r1-1][c1-1]
// Примерна имплементация (1-based index)
vector<vector<long long>> pref(N + 1, vector<long long>(M + 1, 0));
for (int i = 1; i <= N; i++) {
for (int j = 1; j <= M; j++) {
pref[i][j] = matrix[i][j] + pref[i-1][j] + pref[i][j-1] - pref[i-1][j-1];
}
}
// Заявка за (r1, c1) до (r2, c2)
long long query(int r1, int c1, int r2, int c2) {
return pref[r2][c2] - pref[r1-1][c2] - pref[r2][c1-1] + pref[r1-1][c1-1];
}
4. Компресия на координати (Coordinate Compression)
Ако координатите са големи (напр. до \(10^9\)), но броят на правоъгълниците \(N\) е малък (напр. \(1000\)), не можем да създадем матрица с размер \(10^9 \times 10^9\). Но реално важните координати са само тези, където започва или свършва правоъгълник (\(x_1\) и \(x_2\)). Има най-много \(2N\) уникални X и \(2N\) уникални Y координати.
Алгоритъм:
- Съберете всички \(x\) координати във вектор
xs. - Сортирайте
xsи премахнете дубликатите (sort+unique). - Заменете всяка координата \(x\) от входа с нейния индекс в
xs(чрезlower_bound). - Сега работите с координати в интервала \([0, 2N]\).
vector<int> xs;
for (auto r : rects) { xs.push_back(r.x1); xs.push_back(r.x2); }
sort(xs.begin(), xs.end());
xs.erase(unique(xs.begin(), xs.end()), xs.end());
// Get compressed value
int compressed_x = lower_bound(xs.begin(), xs.end(), raw_x) - xs.begin();
// Get original value
int original_x = xs[compressed_x];
Това е ключова техника при работа със SegTree или Sweep Line върху големи координати.
5. Задачи
- CSES Forest Queries: Класическа задача за 2D префиксни суми.
- Codeforces 1195C: Basketball Exercise (има геометрични вариации).
- IOI 2013 Dreaming: Включва работа с метрики.
Съвет: Винаги рисувайте случаите, когато правоъгълниците не се пресичат, за да разберете формулите за max и min при сечението.
6. Въртене на правоъгълник (Rotation)
Ако правоъгълникът не е axis-aligned (страните не са успоредни на осите), задачите стават значително по-сложни. Често се налага: * Използване на линейна алгебра (матрици за трансформации). * Проверка за сечение чрез Separating Axis Theorem (SAT).
За основни задачи с въртене на 90°/180°/270°:
// Въртене на точка (x, y) с 90° около началото
pair<int,int> rotate90(int x, int y) {
return {-y, x};
}
7. Минимална ограждаща кутия (Bounding Box)
Ако имаме множество от точки, най-малкият axis-aligned правоъгълник, който ги съдържа, се намира лесно:
Rect boundingBox(vector<Point>& points) {
int minX = INT_MAX, minY = INT_MAX;
int maxX = INT_MIN, maxY = INT_MIN;
for (auto& p : points) {
minX = min(minX, p.x);
minY = min(minY, p.y);
maxX = max(maxX, p.x);
maxY = max(maxY, p.y);
}
return {minX, minY, maxX, maxY};
}
8. Проблеми с точност при координати
Ако координатите са реални числа (double), внимавайте с проверките за равенство:
При работа с цели координати (\(10^9\)), площта може да достигне \(10^{18}\), което се побира в long long. Но при още по-големи координати може да се наложи BigInt или модулна аритметика.
9. Разширени задачи
9.1. Площ на обединение на \(N\) правоъгълника
За това се използва Sweep Line алгоритъм (вижте Тема 27): * Събираме всички вертикални ръбове (откриващи и затварящи). * Сортираме ги по X координата. * Поддържаме активни интервали по Y оста чрез Segment Tree или друга структура.
Сложност: \(O(N \log N)\).
9.2. Брой точки в правоъгълник (Range Query 2D)
Ако имаме \(M\) заявки за брой точки в даден правоъгълник: * Офлайн метод: Използваме Sweep Line + Fenwick Tree. * Онлайн метод: 2D Range Tree или Fractional Cascading.
🏁 Заключение
Работата с правоъгълници е основна в много области на състезателното програмиране. Владеенето на: * Формулите за сечение и обединение. * 2D префиксни суми. * Компресия на координати.
...ще ви позволи да решавате широк спектър от геометрични и оптимизационни задачи. Винаги тествайте с ръбни случаи (правоъгълници, които споделят само един връх, напълно припокриващи се и т.н.).