OI XXXIII - zli (Alt)

// https://szkopul.edu.pl/problemset/problem/1M2UtUN-WqIVZu5YBXvVvrJw/site/?key=statement

#include <bits/stdc++.h>

using namespace std;

struct Point {
    long long x, y;
};

struct Segment {
    int u, v;
    long long dx, dy;
    long long rdx, rdy;

    long long h;
    long long l;
    long long r;
};

int n;
vector<Point> points;
vector<Segment> segments;

vector<int> bit;
int bit_n;

void bit_update(int idx, int val) {
    for (; idx <= bit_n; idx += idx & -idx)
        bit[idx] += val;
}

int bit_query(int idx) {
    int s = 0;
    for (; idx > 0; idx -= idx & -idx)
        s += bit[idx];
    return s;
}

int main() {
    ios_base::sync_with_stdio(false);
    cin.tie(NULL);

    cin >> n;
    points.resize(n);
    for (int i = 0; i < n; ++i) {
        cin >> points[i].x >> points[i].y;
    }

    if (n < 4) {
        cout << 0 << endl;
        return 0;
    }

    segments.reserve(n * (n - 1));
    for (int i = 0; i < n; ++i) {
        for (int j = 0; j < n; ++j) {
            if (i == j) continue;

            long long dx = points[j].x - points[i].x;
            long long dy = points[j].y - points[i].y;

            long long g = std::gcd(std::abs(dx), std::abs(dy));

            segments.push_back({i, j, dx, dy, dx / g, dy / g, 0, 0, 0});
        }
    }

    sort(segments.begin(), segments.end(), [](const Segment& a, const Segment& b) {
        if (a.rdx != b.rdx) return a.rdx < b.rdx;
        return a.rdy < b.rdy;
    });

    long long total_z = 0;
    int seg_count = segments.size();
    int i = 0;

    while (i < seg_count) {
        int j = i;
        while (j < seg_count && segments[j].rdx == segments[i].rdx && segments[j].rdy == segments[i].rdy) {
            j++;
        }

        int group_size = j - i;
        if (group_size < 2) {
            i = j;
            continue;
        }

        long long Vx = segments[i].rdx;
        long long Vy = segments[i].rdy;

        vector<Segment*> grp;
        grp.reserve(group_size);

        vector<long long> coords;
        coords.reserve(group_size * 2);

        for (int k = i; k < j; ++k) {
            Segment& s = segments[k];

            s.h = points[s.u].x * Vy - points[s.u].y * Vx;

            s.l = points[s.u].x * Vx + points[s.u].y * Vy;

            s.r = points[s.v].x * Vx + points[s.v].y * Vy;

            coords.push_back(s.l);
            coords.push_back(s.r);
            grp.push_back(&s);
        }

        sort(coords.begin(), coords.end());
        coords.erase(unique(coords.begin(), coords.end()), coords.end());

        auto get_rank = [&](long long val) { return lower_bound(coords.begin(), coords.end(), val) - coords.begin() + 1; };

        sort(grp.begin(), grp.end(), [](const Segment* a, const Segment* b) { return a->h > b->h; });

        bit_n = coords.size();
        bit.assign(bit_n + 1, 0);

        int p = 0;
        while (p < group_size) {
            int q = p;
            while (q < group_size && grp[q]->h == grp[p]->h) {
                q++;
            }

            for (int k = p; k < q; ++k) {
                int r_rank = get_rank(grp[k]->r);
                total_z += bit_query(r_rank - 1);
            }

            for (int k = p; k < q; ++k) {
                int l_rank = get_rank(grp[k]->l);
                bit_update(l_rank, 1);
            }

            p = q;
        }

        i = j;
    }

    cout << total_z << endl;

    return 0;
}