Search for a command to run...
Progressive hints first, then the full explanation and implementation when you're ready to cash out.
Review status
AI-generated and still unreviewed. Double-check the details before internalizing them.
Hints
Open only as much as you need to keep the solve alive.
Try to order the red vertices of a triangle as leftmost, middle, rightmost. Then every triangle becomes an -monotone shape, which is way easier to reason about than a random unordered triple.
For two red points with , define a value like That count is the basic ingredient. Milk it.
Now take a triangle with red vertices in -order . Compare the three values , , and . What does mean geometrically?
If is above segment , then on each vertical slice the triangle is exactly the region between the upper chain and the base . In that case, If is below the base, the same expression becomes the negative of that number. Either way, the triangle is empty iff it equals .
So the whole problem collapses to this: after making all -coordinates distinct, sort red points by , precompute all in , then count triples such that That’s it. The only annoying corner case is equal -coordinates, so apply a shear transform like with a huge constant , which preserves orientation and makes all 's distinct.
A triangle is much easier to analyze if its vertices are ordered by increasing -coordinate: Then the triangle is -monotone: every vertical line intersects it in either nothing or one segment. That’s exactly the kind of geometry we can count with.
There’s one stupid little issue: some points may share the same -coordinate. We kill that deterministically with a shear transform:
where , for example .
Why this works:
So from now on, assume every point has a unique .
Sort the red points by increasing . For every pair , define
Formally, “below” means
Because no three points are collinear, we never have to deal with a blue point sitting exactly on an edge. Thank God.
Take any red triangle, and write its vertices in -order as . Look at
This is the whole problem hiding in one line.
Then the broken chain is the upper boundary of the triangle, and is the base.
For a blue point with , the contribution to is
That is:
But on that -interval, “between those two lines” is exactly “inside the triangle”. The same argument works on using lines and .
So in this case,
Now the chain is the lower boundary of the triangle. The exact same slice argument shows every blue point inside contributes , and every blue point outside contributes . So
In both cases,
So the emptiness condition becomes the gloriously simple
That’s the aha. Everything else is bookkeeping.
Each triangle is counted exactly once because after the shear, the three red vertices have distinct -coordinates, hence a unique order .
We proved the central lemma:
Therefore the expression is zero iff the triangle contains no blue point inside.
Since every red triangle has a unique left-middle-right order after the shear, counting all triples satisfying the equality counts exactly all valid triangles.
Done. Neat, sharp, and not even that much code. Geometry problems occasionally decide not to be assholes.
Precomputing all takes Counting all triples takes So total complexity is
With , this is easily fine.
Memory usage is
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
using i128 = __int128_t;
void setIO() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
}
struct Point {
ll x, y;
};
static const ll SHEAR = 4000000001LL;
Point transform_point(ll x, ll y) {
return {x * SHEAR + y, y};
}
i128 cross(const Point& a, const Point& b, const Point& c) {
return (i128)(b.x - a.x) * (c.y - a.y) - (i128)(b.y - a.y) * (c.x - a.x);
}
int main() {
setIO();
int N, M;
cin >> N >> M;
vector<Point> red(N), blue(M);
for (int i = 0; i < N; ++i) {
ll x, y;
cin >> x >> y;
red[i] = transform_point(x, y);
}
for (int i = 0; i < M; ++i) {
ll x, y;
cin >> x >> y;
blue[i] = transform_point(x, y);
}
sort(red.begin(), red.end(), [](const Point& a, const Point& b) {
return a.x < b.x;
});
vector<vector<int>> f(N, vector<int>(N, 0));
for (int i = 0; i < N; ++i) {
for (int j = i + 1; j < N; ++j) {
int cnt = 0;
for (const auto& p : blue) {
if (red[i].x < p.x && p.x < red[j].x && cross(red[i], red[j], p) < 0) {
++cnt;
}
}
f[i][j] = cnt;
}
}
ll ans = 0;
for (int i = 0; i < N; ++i) {
for (int k = i + 1; k < N; ++k) {
for (int j = k + 1; j < N; ++j) {
if (f[i][k] + f[k][j] == f[i][j]) {
++ans;
}
}
}
}
cout << ans << '\n';
}#include <bits/stdc++.h>
using namespace std;
using ll = long long;
using i128 = __int128_t;
void setIO() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
}
struct Point {
ll x, y;
};
static const ll SHEAR = 4000000001LL;
Point transform_point(ll x, ll y) {
return {x * SHEAR + y, y};
}
i128 cross(const Point& a, const Point& b, const Point& c) {
return (i128)(b.x - a.x) * (c.y - a.y) - (i128)(b.y - a.y) * (c.x - a.x);
}
int main() {
setIO();
int N, M;
cin >> N >> M;
vector<Point> red(N), blue(M);
for (int i = 0; i < N; ++i) {
ll x, y;
cin >> x >> y;
red[i] = transform_point(x, y);
}
for (int i = 0; i < M; ++i) {
ll x, y;
cin >> x >> y;
blue[i] = transform_point(x, y);
}
sort(red.begin(), red.end(), [](const Point& a, const Point& b) {
return a.x < b.x;
});
vector<vector<int>> f(N, vector<int>(N, 0));
for (int i = 0; i < N; ++i) {
for (int j = i + 1; j < N; ++j) {
int cnt = 0;
for (const auto& p : blue) {
if (red[i].x < p.x && p.x < red[j].x && cross(red[i], red[j], p) < 0) {
++cnt;
}
}
f[i][j] = cnt;
}
}
ll ans = 0;
for (int i = 0; i < N; ++i) {
for (int k = i + 1; k < N; ++k) {
for (int j = k + 1; j < N; ++j) {
if (f[i][k] + f[k][j] == f[i][j]) {
++ans;
}
}
}
}
cout << ans << '\n';
}