kz-maxx

Базалық геометрия

Нүкте, вектор, distance, dot, cross, orientation және segment intersection

Нүкте деген не?

Нүкте жазықтықта екі coordinate арқылы беріледі:

(x, y)

Мысалы:

A = (2, 3)
B = (5, 7)

C++ тілінде нүктені struct арқылы сақтау ыңғайлы:

struct Point {
    long long x;
    long long y;
};

Вектор деген не?

Вектор — бағыт пен ұзындықты көрсететін displacement.

Егер A және B нүктелері болса, A-дан B-ға вектор:

AB = (B.x - A.x, B.y - A.y)

Мысалы:

A = (2, 3)
B = (5, 7)

AB = (5 - 2, 7 - 3) = (3, 4)

Point struct және операциялар

struct Point {
    long long x;
    long long y;

    Point operator + (const Point &other) const {
        return {x + other.x, y + other.y};
    }

    Point operator - (const Point &other) const {
        return {x - other.x, y - other.y};
    }
};

Енді былай жазуға болады:

Point v = b - a;

Бұл a нүктесінен b нүктесіне вектор болады.

Екі нүкте арақашықтығы

A(x1, y1) және B(x2, y2) нүктелері арасындағы distance:

dist = sqrt((x2 - x1)^2 + (y2 - y1)^2)

Мысалы:

A = (0, 0)
B = (3, 4)

dist = sqrt(3^2 + 4^2) = 5

Distance коды

double dist(Point a, Point b) {
    long long dx = a.x - b.x;
    long long dy = a.y - b.y;

    return sqrt(dx * dx + dy * dy);
}
Егер тек distance салыстыру керек болса, sqrt қолданбай, квадрат distance алған дұрыс.

Квадрат distance

Көп задачада distance-терді салыстыру жеткілікті. Онда квадрат distance қолданамыз.

long long dist2(Point a, Point b) {
    long long dx = a.x - b.x;
    long long dy = a.y - b.y;

    return dx * dx + dy * dy;
}

Бұл жақсы, себебі:

  • double error жоқ
  • тез жұмыс істейді
  • sqrt керек емес

Скалярлық көбейтінді

Екі вектордың dot product:

dot(a, b) = a.x * b.x + a.y * b.y

C++:

long long dot(Point a, Point b) {
    return a.x * b.x + a.y * b.y;
}

dot не үшін керек?

Dot product екі вектор арасындағы бұрышты түсінуге көмектеседі.

dot > 0 → бұрыш сүйір
dot = 0 → бұрыш 90°
dot < 0 → бұрыш доғал

Сонымен қатар dot projection және perpendicular тексеру үшін қолданылады.

Перпендикуляр тексеру

Егер екі вектор perpendicular болса, олардың dot product мәні 0 болады.

if (dot(a, b) == 0) {
    cout << "perpendicular";
}

Мысалы:

a = (1, 0)
b = (0, 1)

dot = 1 * 0 + 0 * 1 = 0

Векторлық көбейтінді

2D geometry ішінде cross product былай есептеледі:

cross(a, b) = a.x * b.y - a.y * b.x

C++:

long long cross(Point a, Point b) {
    return a.x * b.y - a.y * b.x;
}

cross не үшін керек?

cross бір вектор екіншісіне қатысты қай жаққа бұрылатынын көрсетеді.

cross > 0 → солға бұрылу
cross < 0 → оңға бұрылу
cross = 0 → бір түзудің бойында жатыр

Бұл geometry ішіндегі ең маңызды операциялардың бірі.

Үш нүктенің orientation-ы

A, B, C үш нүктесі үшін C нүктесі бағытталған AB segment-інің сол жағында ма, оң жағында ма екенін білу керек.

Есептейміз:

cross(B - A, C - A)
> 0 → C AB-ның сол жағында
< 0 → C AB-ның оң жағында
= 0 → A, B, C бір түзуде

Orientation коды

long long orient(Point a, Point b, Point c) {
    return cross(b - a, c - a);
}

Қолдану:

long long val = orient(a, b, c);

if (val > 0) {
    cout << "left";
} else if (val < 0) {
    cout << "right";
} else {
    cout << "collinear";
}

Үшбұрыш ауданы

ABC үшбұрышының екі еселенген ауданы:

abs(cross(B - A, C - A))

Ал кәдімгі area:

abs(cross(B - A, C - A)) / 2

C++:

double triangle_area(Point a, Point b, Point c) {
    return abs(cross(b - a, c - a)) / 2.0;
}

Нүкте segment үстінде жатыр ма?

P нүктесі AB segment-інің үстінде жатыр ма, соны тексерейік.

Екі шарт керек:

1. A, B, P бір түзудің бойында жатыр
2. P нүктесі A мен B арасындағы rectangle ішінде жатыр

Бірінші шарт:

orient(A, B, P) = 0

Код: нүкте segment үстінде

bool onSegment(Point a, Point b, Point p) {
    if (orient(a, b, p) != 0) {
        return false;
    }

    return min(a.x, b.x) <= p.x && p.x <= max(a.x, b.x) &&
           min(a.y, b.y) <= p.y && p.y <= max(a.y, b.y);
}

Екі segment intersection

Екі segment берілген:

AB
CD

Олар қиылыса ма, соны анықтау керек.

Негізгі идея: C және D нүктелері AB-ның екі жағында, ал A және B нүктелері CD-ның екі жағында жатуы керек.

Intersection жалпы жағдай

Orientation мәндерін санаймыз:

orient(a, b, c)
orient(a, b, d)
orient(c, d, a)
orient(c, d, b)

Егер sign-дары әртүрлі болса, segment-тер қиылысады.

Бірақ collinear жағдайларды бөлек қарау керек.

Код: segment intersection

int sign(long long x) {
    if (x > 0) {
        return 1;
    }

    if (x < 0) {
        return -1;
    }

    return 0;
}

bool segmentsIntersect(Point a, Point b, Point c, Point d) {
    long long o1 = orient(a, b, c);
    long long o2 = orient(a, b, d);
    long long o3 = orient(c, d, a);
    long long o4 = orient(c, d, b);

    if (sign(o1) * sign(o2) < 0 && sign(o3) * sign(o4) < 0) {
        return true;
    }

    if (onSegment(a, b, c)) {
        return true;
    }

    if (onSegment(a, b, d)) {
        return true;
    }

    if (onSegment(c, d, a)) {
        return true;
    }

    if (onSegment(c, d, b)) {
        return true;
    }

    return false;
}

Segment intersection толық реализация

#include <bits/stdc++.h>
using namespace std;

struct Point {
    long long x;
    long long y;

    Point operator - (const Point &other) const {
        return {x - other.x, y - other.y};
    }
};

long long cross(Point a, Point b) {
    return a.x * b.y - a.y * b.x;
}

long long orient(Point a, Point b, Point c) {
    return cross(b - a, c - a);
}

int sign(long long x) {
    if (x > 0) {
        return 1;
    }

    if (x < 0) {
        return -1;
    }

    return 0;
}

bool onSegment(Point a, Point b, Point p) {
    if (orient(a, b, p) != 0) {
        return false;
    }

    return min(a.x, b.x) <= p.x && p.x <= max(a.x, b.x) &&
           min(a.y, b.y) <= p.y && p.y <= max(a.y, b.y);
}

bool segmentsIntersect(Point a, Point b, Point c, Point d) {
    long long o1 = orient(a, b, c);
    long long o2 = orient(a, b, d);
    long long o3 = orient(c, d, a);
    long long o4 = orient(c, d, b);

    if (sign(o1) * sign(o2) < 0 && sign(o3) * sign(o4) < 0) {
        return true;
    }

    if (onSegment(a, b, c)) {
        return true;
    }

    if (onSegment(a, b, d)) {
        return true;
    }

    if (onSegment(c, d, a)) {
        return true;
    }

    if (onSegment(c, d, b)) {
        return true;
    }

    return false;
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    Point a, b, c, d;

    cin >> a.x >> a.y;
    cin >> b.x >> b.y;
    cin >> c.x >> c.y;
    cin >> d.x >> d.y;

    if (segmentsIntersect(a, b, c, d)) {
        cout << "YES";
    } else {
        cout << "NO";
    }

    return 0;
}

Көпбұрыш ауданы

Егер polygon vertex-тері ретімен берілсе, area-ны cross қосындысы арқылы табуға болады.

Формула:

area2 = abs(sum(cross(p[i], p[i + 1])))

Бұл екі еселенген area. Кәдімгі area:

area = area2 / 2

Код: polygon area

long long polygon_area2(vector<Point> p) {
    int n = p.size();

    long long s = 0;

    for (int i = 0; i < n; i++) {
        Point a = p[i];
        Point b = p[(i + 1) % n];

        s += cross(a, b);
    }

    return abs(s);
}

Кәдімгі area шығару:

long long area2 = polygon_area2(p);

if (area2 % 2 == 0) {
    cout << area2 / 2;
} else {
    cout << area2 / 2 << ".5";
}

Area мысалы

Квадрат:

(0,0)
(2,0)
(2,2)
(0,2)

Оның area:

4

Екі еселенген area:

8

Дөңес көпбұрыш

Дөңес көпбұрыш — барлық бұрылыс бір бағытта болатын polygon.

Тексеру:

әр көрші үш нүкте үшін orient санаймыз
барлық нөл емес sign бірдей болуы керек

Код: convex тексеру

bool isConvex(vector<Point> p) {
    int n = p.size();

    int last = 0;

    for (int i = 0; i < n; i++) {
        Point a = p[i];
        Point b = p[(i + 1) % n];
        Point c = p[(i + 2) % n];

        int cur = sign(orient(a, b, c));

        if (cur == 0) {
            continue;
        }

        if (last != 0 && cur != last) {
            return false;
        }

        last = cur;
    }

    return true;
}

Нүкте polygon ішінде ме?

Кез келген polygon үшін ray casting method қолдануға болады.

Идея:

нүктеден оңға қарай ray жібереміз
ол polygon қабырғаларын неше рет қиғанын санаймыз
саны тақ болса → нүкте ішінде
саны жұп болса → нүкте сыртында
Егер нүкте boundary үстінде жатса, оны бөлек өңдейміз.

Код: нүкте polygon ішінде

bool insidePolygon(vector<Point> p, Point q) {
    int n = p.size();
    bool inside = false;

    for (int i = 0; i < n; i++) {
        Point a = p[i];
        Point b = p[(i + 1) % n];

        if (onSegment(a, b, q)) {
            return true;
        }

        bool cond1 = (a.y > q.y) != (b.y > q.y);

        if (cond1) {
            double x = a.x + 1.0 * (b.x - a.x) * (q.y - a.y) / (b.y - a.y);

            if (x > q.x) {
                inside = !inside;
            }
        }
    }

    return inside;
}

Векторлар арасындағы бұрыш

Екі вектор арасындағы angle dot product арқылы табылады:

cos(angle) = dot(a, b) / (|a| * |b|)

Код:

double length(Point a) {
    return sqrt(a.x * a.x + a.y * a.y);
}

double angle(Point a, Point b) {
    double cosValue = dot(a, b) / (length(a) * length(b));

    cosValue = max(-1.0, min(1.0, cosValue));

    return acos(cosValue);
}
max/min double error үшін керек.

Нүктені бұру

(x, y) нүктесін координата басы айналасында angle бұрышқа бұру:

newX = x * cos(angle) - y * sin(angle)
newY = x * sin(angle) + y * cos(angle)

Код:

struct DPoint {
    double x;
    double y;
};

DPoint rotatePoint(DPoint p, double angle) {
    double cs = cos(angle);
    double sn = sin(angle);

    return {
        p.x * cs - p.y * sn,
        p.x * sn + p.y * cs
    };
}

Integer және double geometry

Тип Қашан қолданамыз?
long long coordinates integer, нақты comparison керек
double angle, circle, sqrt, sin, cos керек
Orientation, segment intersection және area үшін coordinates integer болса long long жақсы.

double үшін EPS

double сандарды == арқылы салыстыру қауіпті. Кішкентай error болуы мүмкін. Сондықтан EPS қолданамыз.

const double EPS = 1e-9;

int cmp(double x) {
    if (x > EPS) {
        return 1;
    }

    if (x < -EPS) {
        return -1;
    }

    return 0;
}

Егер cmp(x) == 0 болса, x нөлге тең деп санаймыз.

Базалық geometry template

struct Point {
    long long x;
    long long y;

    Point operator + (const Point &other) const {
        return {x + other.x, y + other.y};
    }

    Point operator - (const Point &other) const {
        return {x - other.x, y - other.y};
    }
};

long long dot(Point a, Point b) {
    return a.x * b.x + a.y * b.y;
}

long long cross(Point a, Point b) {
    return a.x * b.y - a.y * b.x;
}

long long orient(Point a, Point b, Point c) {
    return cross(b - a, c - a);
}

long long dist2(Point a, Point b) {
    long long dx = a.x - b.x;
    long long dy = a.y - b.y;

    return dx * dx + dy * dy;
}

Типтік задачалар

  • екі нүкте арасындағы distance табу
  • нүктелер бір түзуде жатыр ма тексеру
  • солға немесе оңға бұрылуды анықтау
  • segment intersection тексеру
  • үшбұрыш area табу
  • polygon area табу
  • нүкте polygon ішінде ме тексеру
  • polygon convex пе тексеру

Типтік қателер

  • dot пен cross шатастыру
  • cross negative болуы мүмкін екенін ұмыту
  • coordinates үлкен кезде int қолдану
  • double-ды == арқылы салыстыру
  • нүкте segment үстінде жатқан special case ұмыту
  • collinear segment-терді өңдемеу
  • polygon area ішінде соңғы edge p[n-1] → p[0] ұмыту
  • area және екі еселенген area шатастыру

Асимптотика

Операция Уақыт
Екі нүкте арасындағы distance O(1)
dot / cross O(1)
Үш нүкте orientation O(1)
Екі segment intersection O(1)
Polygon area O(n)
Нүкте polygon ішінде ме O(n)
Convex тексеру O(n)

Қашан не қолданамыз?

distance керек → dist немесе dist2
angle тексеру керек → dot
бұрылу бағыты керек → cross / orient
collinear тексеру керек → orient == 0
segment intersection керек → orient + onSegment
area керек → cross суммасы
double болса → EPS арқылы салыстыру

Ең бастысы

нүкте = (x, y)
AB векторы = B - A
dot бұрышты көрсетеді
cross бұрылуды көрсетеді
orient(a,b,c) c нүктесінің ab-ға қатысты орнын көрсетеді
area cross арқылы табылады
segment intersection orient арқылы тексеріледі