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.
Trailing zeros in a product only care about how many times the product is divisible by , i.e. by pairs of and . Try replacing each cell value by two tiny pieces of information: and .
For a fixed path , the number of trailing zeros is
Since you only move right or down, each of those sums screams "grid DP".
You do not need a DP state like unless you enjoy pain. Let
Can you prove that the best positive-path answer is just ?
Now the annoying gremlin: cells equal to . A path through zero makes the whole product , and in this problem that gives an answer of . So if the best path avoiding zero has answer , then forcing a path through any zero is better.
Run two ordinary DPs over the grid: one minimizing total , one minimizing total , and store parents for reconstruction. In those DPs, treat zero cells as blocked / cost , but remember one zero position . If the best DP answer is (or impossible) and a zero exists, print answer and a path like
Otherwise reconstruct the better of the two DP paths.
This problem looks like it wants some cursed DP on gigantic products. It doesnβt. You can throw the actual product in the trash and keep only the part that matters.
For any positive integer , let be the exponent of in its prime factorization, and the exponent of .
If a path uses cells , then for the product along that path:
The number of trailing zeros of a positive product is exactly
So each cell contributes only two small costs: how many 's and how many 's it contains.
This is the real aha. A lot of people see
and immediately start inventing some monstrous DP state with both values. Donβt. Thatβs overkill.
Let
where ranges over valid paths with non-zero product for now.
Then the optimal answer among those paths is exactly
Why?
For every path , we have and , so
Now take a path that achieves . Then
Similarly, a path achieving satisfies
So the optimum is both at least and at most . Therefore it is exactly .
Thatβs the whole trick. Two ordinary DPs, one for 's and one for 's. No black magic.
For each non-zero cell define
Then run two grid DPs:
Boundary cells only have one predecessor, obviously.
To print the path, store a parent for each state:
'D' if the best predecessor is from above, i.e. from ,'R' if it is from the left, i.e. from .Then backtrack from to and reverse the string.
A value is special because factor counts are not useful there, and the product becomes .
The standard trick for this problem is:
After the DPs, let
If a zero exists and , then going through that zero gives product , and for this problem that is used as answer . So it beats anything with at least trailing zeros.
So in that case we simply output:
This also cleanly handles cases where the start or finish cell itself is zero: the positive-product DP becomes impossible, and the forced-zero path gives answer .
If , the zero trick is useless, so just print the better reconstructed DP path.
Thatβs it. The problem looks scary because of the product, but the product is mostly bullshit noise.
For each of the cells we do constant DP work, plus counting factors of and in a number up to , which is tiny.
So the total complexity is
time and
memory.
With , thatβs completely fine.
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
void setIO() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
}
int main() { setIO();
int n;
cin >> n;
const int INF = 1e9;
vector<vector<int>> dp2(n, vector<int>(n, INF));
vector<vector<int>> dp5(n, vector<int>(n, INF));
vector<string> par2(n, string(n, '?'));
vector<string> par5(n, string(n, '?'));
auto cnt = [&](int x, int p) {
int c = 0;
while (x % p == 0) {
x /= p;
++c;
}
return c;
};
bool hasZero = false;
int zx = -1, zy = -1;
auto relax = [&](int i, int j, int cost, vector<vector<int>>& dp, vector<string>& par) {
if (i == 0 && j == 0) {
dp[i][j] = cost;
return;
}
int best = INF;
// parent stores the forward move used to reach (i, j)
// 'D' => came from (i-1, j), 'R' => came from (i, j-1)
if (i > 0 && dp[i - 1][j] < best) {
best = dp[i - 1][j];
par[i][j] = 'D';
}
if (j > 0 && dp[i][j - 1] < best) {
best = dp[i][j - 1];
par[i][j] = 'R';
}
if (best >= INF || cost >= INF) dp[i][j] = INF;
else dp[i][j] = best + cost;
};
for (int i = 0; i < n; ++i) {
for (int j = 0; j < n; ++j) {
int a;
cin >> a;
int c2, c5;
if (a == 0) {
hasZero = true;
zx = i;
zy = j;
c2 = c5 = INF; // block zero in the positive-product DP
} else {
c2 = cnt(a, 2);
c5 = cnt(a, 5);
}
relax(i, j, c2, dp2, par2);
relax(i, j, c5, dp5, par5);
}
}
auto build = [&](const vector<string>& par) {
string s;
int i = n - 1, j = n - 1;
while (i > 0 || j > 0) {
char c = par[i][j];
s.push_back(c);
if (c == 'D') --i;
else --j;
}
reverse(s.begin(), s.end());
return s;
};
int ans2 = dp2[n - 1][n - 1];
int ans5 = dp5[n - 1][n - 1];
int best = min(ans2, ans5);
if (hasZero && best > 1) {
cout << 1 << '\n';
string path;
path += string(zx, 'D');
path += string(zy, 'R');
path += string(n - 1 - zx, 'D');
path += string(n - 1 - zy, 'R');
cout << path << '\n';
return 0;
}
if (ans2 < ans5) {
cout << ans2 << '\n' << build(par2) << '\n';
} else {
cout << ans5 << '\n' << build(par5) << '\n';
}
}#include <bits/stdc++.h>
using namespace std;
using ll = long long;
void setIO() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
}
int main() { setIO();
int n;
cin >> n;
const int INF = 1e9;
vector<vector<int>> dp2(n, vector<int>(n, INF));
vector<vector<int>> dp5(n, vector<int>(n, INF));
vector<string> par2(n, string(n, '?'));
vector<string> par5(n, string(n, '?'));
auto cnt = [&](int x, int p) {
int c = 0;
while (x % p == 0) {
x /= p;
++c;
}
return c;
};
bool hasZero = false;
int zx = -1, zy = -1;
auto relax = [&](int i, int j, int cost, vector<vector<int>>& dp, vector<string>& par) {
if (i == 0 && j == 0) {
dp[i][j] = cost;
return;
}
int best = INF;
// parent stores the forward move used to reach (i, j)
// 'D' => came from (i-1, j), 'R' => came from (i, j-1)
if (i > 0 && dp[i - 1][j] < best) {
best = dp[i - 1][j];
par[i][j] = 'D';
}
if (j > 0 && dp[i][j - 1] < best) {
best = dp[i][j - 1];
par[i][j] = 'R';
}
if (best >= INF || cost >= INF) dp[i][j] = INF;
else dp[i][j] = best + cost;
};
for (int i = 0; i < n; ++i) {
for (int j = 0; j < n; ++j) {
int a;
cin >> a;
int c2, c5;
if (a == 0) {
hasZero = true;
zx = i;
zy = j;
c2 = c5 = INF; // block zero in the positive-product DP
} else {
c2 = cnt(a, 2);
c5 = cnt(a, 5);
}
relax(i, j, c2, dp2, par2);
relax(i, j, c5, dp5, par5);
}
}
auto build = [&](const vector<string>& par) {
string s;
int i = n - 1, j = n - 1;
while (i > 0 || j > 0) {
char c = par[i][j];
s.push_back(c);
if (c == 'D') --i;
else --j;
}
reverse(s.begin(), s.end());
return s;
};
int ans2 = dp2[n - 1][n - 1];
int ans5 = dp5[n - 1][n - 1];
int best = min(ans2, ans5);
if (hasZero && best > 1) {
cout << 1 << '\n';
string path;
path += string(zx, 'D');
path += string(zy, 'R');
path += string(n - 1 - zx, 'D');
path += string(n - 1 - zy, 'R');
cout << path << '\n';
return 0;
}
if (ans2 < ans5) {
cout << ans2 << '\n' << build(par2) << '\n';
} else {
cout << ans5 << '\n' << build(par5) << '\n';
}
}