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.
Adding ballerinas means adding full rows/columns around the old rectangle. The original grid stays one solid block; nobody gets inserted into the middle.
Let be the vertical displacement between the old grid and its rotated copy, and the horizontal displacement. For fixed , the cheapest final rectangle has size by .
A shift is valid exactly when every old cell whose rotated partner also lands inside the old grid has the same letter as that partner. Cells outside the old grid can be filled freely.
Make be the 180-degree rotation of . Then shift is valid iff the overlap of equals the matching overlap of shifted by .
There are only possible overlapping shifts. Try them all, compare each overlap in with a 2D rolling hash, and minimize .
Let's strip the ballet lore down to geometry. The dancers are just a rectangle with commitment issues.
Geometry
Suppose we add rows above the original grid, rows below it, columns to the left, and columns to the right.
The original grid stays as one contiguous by block inside the final rectangle. After a 180-degree rotation, that original block lands on another by block. Relative to the original block, the rotated copy is shifted by
For a fixed , the cheapest way to get uses exactly added rows. Same for columns. So if shift is possible, its best cost is
Now the whole problem is: which shifts are possible?
Forced Overlaps
For shift , an original cell rotates to
If that position is outside the original grid, no issue. We can invite a new ballerina there with the required letter.
If that position is inside the original grid, then we are stuck with the existing letter. So the two letters must match. If not, this shift is impossible. Extra padding cannot fix a contradiction between two already-existing cells. Brutal, but fair.
The statement also requires at least one original ballerina to land on an original position. That means the original block and its rotated copy must overlap, so
The overlap size is then
Subrectangle Equality
Let be the 180-degree rotation of :
The condition above becomes
for every overlapping cell.
So for every shift, we only need to compare one subrectangle of with one subrectangle of .
For rows, the subrectangle in starts at and has height . The matching subrectangle in starts at . Columns work the same way with .
Trying Every Shift Is Fine
The number of shifts is
Since the total over all test cases is at most , this is completely fine. The only thing we need is fast overlap comparison; checking cells one by one for every shift would be a very expensive way to be sad.
2D Hashing
Build a 2D rolling hash for and for . For a matrix , define prefix hash by
Then the normalized hash of the rectangle starting at with height and width is
This hash is independent of the rectangle's absolute position, so equal subrectangles get equal hashes. The solution uses two independent unsigned 64-bit hashes. In contest practice, that is plenty; if you want deterministic-looking arithmetic, two large prime moduli are a drop-in replacement.
Algorithm
Correctness
Any valid final rectangle gives some shift between the original block and its rotated copy. Every place where those two blocks overlap is an old-old forced comparison, so the letters must match. Therefore our hash test accepts every shift that can come from a valid construction, and the construction must pay at least extra rows and extra columns.
Conversely, if our hash test accepts a shift, all old-old forced comparisons are consistent. Every old-new rotated pair can be filled with the needed letter, and every new-new pair can be filled arbitrarily but symmetrically. So a valid final rectangle exists with exactly invited dancers.
Thus minimizing over all accepted shifts gives the true minimum.
Complexity
Hash construction is . There are fewer than shifts, each checked in . Memory usage is .
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
using ull = unsigned long long;
void setIO() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
}
struct HashVal {
ull a = 0, b = 0;
bool operator==(const HashVal& other) const {
return a == other.a && b == other.b;
}
};
const int MAXD = 1'000'000 + 5;
const ull ROW1 = 11995408973635179863ull;
const ull COL1 = 10150724397891781847ull;
const ull ROW2 = 15007676735111374503ull;
const ull COL2 = 7957224627758338711ull;
vector<HashVal> powRow(MAXD), powCol(MAXD);
vector<HashVal> buildHash(const vector<string>& grid, bool reversed) {
int n = (int)grid.size();
int m = (int)grid[0].size();
int stride = m + 1;
vector<HashVal> pref((n + 1) * stride);
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
ull v;
if (!reversed) v = (ull)(grid[i][j] - 'a' + 1);
else v = (ull)(grid[n - 1 - i][m - 1 - j] - 'a' + 1);
const HashVal& top = pref[i * stride + (j + 1)];
const HashVal& left = pref[(i + 1) * stride + j];
const HashVal& diag = pref[i * stride + j];
HashVal cur;
cur.a = v + top.a * ROW1 + left.a * COL1 - diag.a * ROW1 * COL1;
cur.b = v + top.b * ROW2 + left.b * COL2 - diag.b * ROW2 * COL2;
pref[(i + 1) * stride + (j + 1)] = cur;
}
}
return pref;
}
HashVal rectHash(const vector<HashVal>& pref, int stride, int r, int c, int h, int w) {
const HashVal& all = pref[(r + h) * stride + (c + w)];
const HashVal& top = pref[r * stride + (c + w)];
const HashVal& left = pref[(r + h) * stride + c];
const HashVal& diag = pref[r * stride + c];
HashVal res;
res.a = all.a
- top.a * powRow[h].a
- left.a * powCol[w].a
+ diag.a * powRow[h].a * powCol[w].a;
res.b = all.b
- top.b * powRow[h].b
- left.b * powCol[w].b
+ diag.b * powRow[h].b * powCol[w].b;
return res;
}
int main() {
setIO();
powRow[0] = {1, 1};
powCol[0] = {1, 1};
for (int i = 1; i < MAXD; i++) {
powRow[i].a = powRow[i - 1].a * ROW1;
powRow[i].b = powRow[i - 1].b * ROW2;
powCol[i].a = powCol[i - 1].a * COL1;
powCol[i].b = powCol[i - 1].b * COL2;
}
int t;
cin >> t;
while (t--) {
int n, m;
cin >> n >> m;
vector<string> grid(n);
for (string& row : grid) cin >> row;
vector<HashVal> hashA = buildHash(grid, false);
vector<HashVal> hashB = buildHash(grid, true);
int stride = m + 1;
ll best = (1LL << 62);
for (int x = 1 - n; x <= n - 1; x++) {
int ax = abs(x);
int h = n - ax;
int ar = max(0, x);
int br = ar - x;
for (int y = 1 - m; y <= m - 1; y++) {
int ay = abs(y);
int w = m - ay;
int ac = max(0, y);
int bc = ac - y;
if (rectHash(hashA, stride, ar, ac, h, w) ==
rectHash(hashB, stride, br, bc, h, w)) {
ll cost = 1LL * (n + ax) * (m + ay) - 1LL * n * m;
best = min(best, cost);
}
}
}
cout << best << '\n';
}
return 0;
}#include <bits/stdc++.h>
using namespace std;
using ll = long long;
using ull = unsigned long long;
void setIO() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
}
struct HashVal {
ull a = 0, b = 0;
bool operator==(const HashVal& other) const {
return a == other.a && b == other.b;
}
};
const int MAXD = 1'000'000 + 5;
const ull ROW1 = 11995408973635179863ull;
const ull COL1 = 10150724397891781847ull;
const ull ROW2 = 15007676735111374503ull;
const ull COL2 = 7957224627758338711ull;
vector<HashVal> powRow(MAXD), powCol(MAXD);
vector<HashVal> buildHash(const vector<string>& grid, bool reversed) {
int n = (int)grid.size();
int m = (int)grid[0].size();
int stride = m + 1;
vector<HashVal> pref((n + 1) * stride);
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
ull v;
if (!reversed) v = (ull)(grid[i][j] - 'a' + 1);
else v = (ull)(grid[n - 1 - i][m - 1 - j] - 'a' + 1);
const HashVal& top = pref[i * stride + (j + 1)];
const HashVal& left = pref[(i + 1) * stride + j];
const HashVal& diag = pref[i * stride + j];
HashVal cur;
cur.a = v + top.a * ROW1 + left.a * COL1 - diag.a * ROW1 * COL1;
cur.b = v + top.b * ROW2 + left.b * COL2 - diag.b * ROW2 * COL2;
pref[(i + 1) * stride + (j + 1)] = cur;
}
}
return pref;
}
HashVal rectHash(const vector<HashVal>& pref, int stride, int r, int c, int h, int w) {
const HashVal& all = pref[(r + h) * stride + (c + w)];
const HashVal& top = pref[r * stride + (c + w)];
const HashVal& left = pref[(r + h) * stride + c];
const HashVal& diag = pref[r * stride + c];
HashVal res;
res.a = all.a
- top.a * powRow[h].a
- left.a * powCol[w].a
+ diag.a * powRow[h].a * powCol[w].a;
res.b = all.b
- top.b * powRow[h].b
- left.b * powCol[w].b
+ diag.b * powRow[h].b * powCol[w].b;
return res;
}
int main() {
setIO();
powRow[0] = {1, 1};
powCol[0] = {1, 1};
for (int i = 1; i < MAXD; i++) {
powRow[i].a = powRow[i - 1].a * ROW1;
powRow[i].b = powRow[i - 1].b * ROW2;
powCol[i].a = powCol[i - 1].a * COL1;
powCol[i].b = powCol[i - 1].b * COL2;
}
int t;
cin >> t;
while (t--) {
int n, m;
cin >> n >> m;
vector<string> grid(n);
for (string& row : grid) cin >> row;
vector<HashVal> hashA = buildHash(grid, false);
vector<HashVal> hashB = buildHash(grid, true);
int stride = m + 1;
ll best = (1LL << 62);
for (int x = 1 - n; x <= n - 1; x++) {
int ax = abs(x);
int h = n - ax;
int ar = max(0, x);
int br = ar - x;
for (int y = 1 - m; y <= m - 1; y++) {
int ay = abs(y);
int w = m - ay;
int ac = max(0, y);
int bc = ac - y;
if (rectHash(hashA, stride, ar, ac, h, w) ==
rectHash(hashB, stride, br, bc, h, w)) {
ll cost = 1LL * (n + ax) * (m + ay) - 1LL * n * m;
best = min(best, cost);
}
}
}
cout << best << '\n';
}
return 0;
}