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.
For two endpoints, forget paths for a second. The set of integer points that can appear on some shortest path is exactly the whole axis-aligned rectangle between them.
So a pair is good iff its bounding rectangle is completely filled with black points. If the endpoint coordinates differ by , that rectangle needs black points. Sparse coordinate gaps instantly kill most pairs.
Split the answer by orientation. Horizontal and vertical pairs are just contiguous runs of points with consecutive integer coordinates on the same row/column. The hard part is rectangles with positive width and height.
For non-degenerate rectangles, every used -coordinate and every used -coordinate inside the rectangle must be consecutive integers. Process consecutive rows as bitsets/sets of columns: between two rows , a valid rectangle can only use columns that are present in every row from to .
Use the classic “small-to-large intersection” trick. Compress each maximal block of consecutive rows, store each row as sorted columns, and enumerate row intervals by repeatedly intersecting with the smaller current set. Every time you have the common columns for a row interval, contiguous column runs of length contribute ordered diagonal-corner pairs. Add horizontal/vertical runs separately.
We need count ordered pairs of black points such that every integer point on any shortest robot path from to is black.
The important deconstruction: this is not asking for “there exists a clean shortest path”. It says any shortest path. That is way stricter.
Suppose and , with and . A shortest path uses exactly right moves and up moves, in any order. For any integer point inside the rectangle
there is some shortest path that visits it: go to first, then finish. So the union of all shortest paths is the whole rectangle.
Therefore a pair is good iff its closed axis-aligned integer rectangle is completely black.
That one sentence is the whole problem. The rest is making it fast enough without summoning quadratic hell.
Degenerate rectangles
If two points are on the same row, we only need the horizontal segment between them to be fully black. On each row, sort all values. Every maximal run of consecutive integers of length contributes
ordered pairs.
Same for columns, using sorted values.
These handle width or height .
Real rectangles
Now consider rectangles with positive width and height.
A rectangle from rows to is possible only if every row between them exists and is consecutive. If there is a missing row, the rectangle would contain white points. So we split the grid into blocks of consecutive occupied row numbers. Different blocks never interact for non-degenerate rectangles.
Inside one block of consecutive rows, store for each row the sorted set of black columns on that row.
For a fixed row interval , the columns that can be used by a full rectangle are exactly
If a column is missing from one of these rows, any rectangle using that column has a white point. Among the common columns, we also need consecutive columns. If has a maximal consecutive run of length , then choosing two different columns from this run gives a positive-width full rectangle over rows .
For each unordered pair of columns, there are exactly two ordered endpoint pairs with positive slope/negative slope corners? More directly: if columns are and rows are , the good ordered pairs are
So every unordered column pair contributes . Since unordered pairs live inside a run, the contribution is
But if we count ordered column choices directly, that is , and then multiply by for the two choices of row direction. Same thing.
Avoiding too much work
Naively intersecting all row intervals is too slow. The useful trick is to enumerate from each starting row and maintain the current intersection as a sorted vector. Intersect the current vector with the next row. The intersection size can only shrink.
The total work is acceptable if we always intersect sorted vectors linearly and stop when the intersection becomes smaller than , because then no positive-width rectangle can remain.
Why does this pass? A point can only survive while it is present in all rows of the current interval. Dense blocks shrink slowly, but then the number of rows/columns is bounded by the total number of points in the block. Sparse blocks die quickly. This is the exact “pay for surviving intersections” geometry trick: no magic, just making the obvious rectangle condition computable.
For each row interval with height at least and at least two common columns, compute the lengths of consecutive runs inside the sorted common-column vector and add .
Finally:
All counts fit in long long: the answer is at most ordered pairs.
Algorithm summary
For each test case:
The implementation uses only sorting, hash maps for grouping, and linear sorted-vector intersections. Pretty clean for a 2800. The statement looks scary; the rectangle observation is the whole boss fight.
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
void setIO() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
}
static ll countRunsOrdered(const vector<int>& v) {
ll res = 0;
int n = (int)v.size();
for (int i = 0; i < n; ) {
int j = i + 1;
while (j < n && v[j] == v[j - 1] + 1) j++;
ll len = j - i;
res += len * (len - 1);
i = j;
}
return res;
}
static vector<int> intersectSorted(const vector<int>& a, const vector<int>& b) {
vector<int> c;
c.reserve(min(a.size(), b.size()));
int i = 0, j = 0;
while (i < (int)a.size() && j < (int)b.size()) {
if (a[i] == b[j]) {
c.push_back(a[i]);
i++, j++;
} else if (a[i] < b[j]) {
i++;
} else {
j++;
}
}
return c;
}
int main() {
setIO();
int T;
cin >> T;
while (T--) {
int n;
cin >> n;
vector<pair<ll, ll>> pts(n);
vector<ll> ys, xs;
ys.reserve(n);
xs.reserve(n);
for (auto& [x, y] : pts) {
cin >> x >> y;
xs.push_back(x);
ys.push_back(y);
}
sort(xs.begin(), xs.end());
xs.erase(unique(xs.begin(), xs.end()), xs.end());
sort(ys.begin(), ys.end());
ys.erase(unique(ys.begin(), ys.end()), ys.end());
auto getX = [&](ll x) {
return (int)(lower_bound(xs.begin(), xs.end(), x) - xs.begin());
};
auto getY = [&](ll y) {
return (int)(lower_bound(ys.begin(), ys.end(), y) - ys.begin());
};
vector<vector<int>> rows(ys.size()), cols(xs.size());
for (auto [x, y] : pts) {
int cx = getX(x), cy = getY(y);
rows[cy].push_back(cx);
cols[cx].push_back(cy);
}
for (auto& r : rows) sort(r.begin(), r.end());
for (auto& c : cols) sort(c.begin(), c.end());
ll ans = 0;
for (auto& r : rows) ans += countRunsOrdered(r);
for (auto& c : cols) ans += countRunsOrdered(c);
int m = (int)rows.size();
for (int blockL = 0; blockL < m; ) {
int blockR = blockL;
while (blockR + 1 < m && ys[blockR + 1] == ys[blockR] + 1) blockR++;
for (int l = blockL; l <= blockR; l++) {
vector<int> common = rows[l];
for (int r = l + 1; r <= blockR; r++) {
common = intersectSorted(common, rows[r]);
if ((int)common.size() < 2) break;
ans += 2 * countRunsOrdered(common);
}
}
blockL = blockR + 1;
}
cout << ans << '\n';
}
}#include <bits/stdc++.h>
using namespace std;
using ll = long long;
void setIO() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
}
static ll countRunsOrdered(const vector<int>& v) {
ll res = 0;
int n = (int)v.size();
for (int i = 0; i < n; ) {
int j = i + 1;
while (j < n && v[j] == v[j - 1] + 1) j++;
ll len = j - i;
res += len * (len - 1);
i = j;
}
return res;
}
static vector<int> intersectSorted(const vector<int>& a, const vector<int>& b) {
vector<int> c;
c.reserve(min(a.size(), b.size()));
int i = 0, j = 0;
while (i < (int)a.size() && j < (int)b.size()) {
if (a[i] == b[j]) {
c.push_back(a[i]);
i++, j++;
} else if (a[i] < b[j]) {
i++;
} else {
j++;
}
}
return c;
}
int main() {
setIO();
int T;
cin >> T;
while (T--) {
int n;
cin >> n;
vector<pair<ll, ll>> pts(n);
vector<ll> ys, xs;
ys.reserve(n);
xs.reserve(n);
for (auto& [x, y] : pts) {
cin >> x >> y;
xs.push_back(x);
ys.push_back(y);
}
sort(xs.begin(), xs.end());
xs.erase(unique(xs.begin(), xs.end()), xs.end());
sort(ys.begin(), ys.end());
ys.erase(unique(ys.begin(), ys.end()), ys.end());
auto getX = [&](ll x) {
return (int)(lower_bound(xs.begin(), xs.end(), x) - xs.begin());
};
auto getY = [&](ll y) {
return (int)(lower_bound(ys.begin(), ys.end(), y) - ys.begin());
};
vector<vector<int>> rows(ys.size()), cols(xs.size());
for (auto [x, y] : pts) {
int cx = getX(x), cy = getY(y);
rows[cy].push_back(cx);
cols[cx].push_back(cy);
}
for (auto& r : rows) sort(r.begin(), r.end());
for (auto& c : cols) sort(c.begin(), c.end());
ll ans = 0;
for (auto& r : rows) ans += countRunsOrdered(r);
for (auto& c : cols) ans += countRunsOrdered(c);
int m = (int)rows.size();
for (int blockL = 0; blockL < m; ) {
int blockR = blockL;
while (blockR + 1 < m && ys[blockR + 1] == ys[blockR] + 1) blockR++;
for (int l = blockL; l <= blockR; l++) {
vector<int> common = rows[l];
for (int r = l + 1; r <= blockR; r++) {
common = intersectSorted(common, rows[r]);
if ((int)common.size() < 2) break;
ans += 2 * countRunsOrdered(common);
}
}
blockL = blockR + 1;
}
cout << ans << '\n';
}
}