Olympic Sinh Viên 2020 - Chuyên tin - Đường đi
===
[https://oj.vnoi.info/problem/olp_ct20_route](https://oj.vnoi.info/problem/olp_ct20_route)
-----
**Credit:** [@darkkcyan](https://codeforces.com/profile/darkkcyan)
-----
### Hướng dẫn tính giá trị
Ta định nghĩa
- $P$ là giá trị tích các số trên đường đi hiện tại
- $V$ là số số $0$ ở cuối $P$
- $c_x$ là số thừa số $x$ trong $P$
- $m_x$ là số lượng tối thiểu lấy được số $x$ trên đường đi bất kì
Nếu trên đường đi có $c_0 > 0$ thì $V = 1$
Nếu trên đường đi có $c_0 = 0$ thì $P = a \times 10^{c_{10}} = b \times 2^{c_2} \times 5^{c_5} \Rightarrow V = c_{10} = min(c_2, c_5)$ với $a, b$ là giá trị b
- Không mất tính tổng quát, giả sử $m_2 \leq m_5$. Nếu ta chọn $c_2 > m_2$ thì đường đi hiện tại là không tối ưu vì $min(c_2, c_5) = c_5 > m_2 = min(m_2, m_5)$
- Chứng minh tương tự với $m_2 \geq m_5$, nếu chọn $c_5 > m_5$ thì đường đi này không tối ưu
- Vậy đường đi có $V$ nhỏ nhất ở trong ít nhất 1 trong hai đường thỏa mãn $c_2 = m_2$ hoặc $c_5 = v_5$
Vậy ta chỉ cần xét 3 đường đi là có thể tính giá trị nhỏ nhất
- Đường có có thứ tự từ điển nhỏ nhất lấy ít thừa số $2$ nhất ($c_0 = 0$ và $c_2 = m_2$)
- Đường có có thứ tự từ điển nhỏ nhất lấy ít thừa số $5$ nhất ($c_0 = 0$ và $c_5 = m_5$)
- Đường có có thứ tự từ điển nhỏ nhất lấy ít nhất một số $0$ ($c_0 > 0$)
Bằng quy hoạch động và BFS, việc tính giá trị rất đơn giản
---
### Hướng dẫn truy vết
Để chọn đường đi có $c_x = m_x$, di chuyển từ $(1, 1)$ đến $(n, m)$ mà có thứ tự từ điển nhỏ nhất, ta làm như sau
- Không mất tính tổng quát coi vị trí hiện tại là $(x, y)$
- Gọi $f(x, y)$ là giá trị nhỏ nhất có thể lấy được khi đi từ $(x, y)$ đến $(n, m)$
- Khi $(x = n)$ thì ta chỉ có thể di chuyển trên hàng, nên đi "**L**"
- Khi $(y = m)$ thì ta chỉ có thể di chuyển trên cột, nên đi "**D**"
- Nếu $f(x + 1, y) = f(x, y + 1)$ thì đi kiểu gì cũng tồn tại đường thỏa $c_x = m_x$ nên ta đi theo đường có thứ tự từ điển nhỏ hơn là "**D**"
- Nếu $f(x + 1, y) > f(x, y + 1)$ thì đường đi theo cột không tối ưu, nên ta đi "**L**"
- Nếu $f(x + 1, y) < f(x, y + 1)$ thì đường đi theo hàng không tối ưu, nên ta đi "**D**"
Để chọn đường đi có $c_x > k$, di chuyển từ $(1, 1)$ đến $(n, m)$ mà có thứ tự từ điển nhỏ nhất, ta làm như sau
- Không mất tính tổng quát coi vị trí hiện tại là $(x, y)$
- Số lượng phần thừa số $x$ lấy được nhiều nhất từ $(1, 1)$ đến $(x, y)$ là $v$
- Gọi $f(x, y)$ là giá trị lớn nhất có thể lấy được khi đi từ $(x, y)$ đến $(n, m)$
- Nếu $f(1, 1) \leq k$ thì không tồn tại đường đi
- Khi $(x = n)$ thì ta chỉ có thể di chuyển trên hàng, nên đi "**L**"
- Khi $(y = m)$ thì ta chỉ có thể di chuyển trên cột, nên đi "**D**"
- Nếu $f(x + 1, y) = f(x, y + 1)$ thì đi kiểu gì cũng tồn tại đường thỏa $c_x = m_x$ nên ta đi theo đường có thứ tự từ điển nhỏ hơn là "**D**"
- Nếu $f(x + 1, y) + v \leq k$ thì đi "**D**" không thì đi "**L**"
-----
### Code
**Bonus:** [Code with checker](https://ideone.com/Wyx6rx)
> **Time:** $O(n \times m \times (log_2(a) + log_5(a) + 1))$
> **Space:** $O(n \times m)$
> **Algo:** DP, Constructive, Medium Implementation, Lexicographical Order
```cpp=
#include <iostream>
#include <cstring>
#include <vector>
#include <deque>
using namespace std;
const int mx[] = {+0, +1};
const int my[] = {+1, +0};
struct Info
{
int x, y, d;
Info (int x = 0, int y = 0, int d = 0)
: x(x), y(y), d(d) {}
};
struct Pack
{
int res;
string way;
Pack (int res = 0, string way = "")
: res(res), way(way) {}
};
Pack min(const Pack &a, const Pack &b)
{
if (a.res < b.res) return a; /// Left is smaller path
if (a.res > b.res) return b; /// Right is smaller path
return (a.way < b.way) ? a : b; /// Choose smaller lexicographical order
}
const int INF = 0x3f3f3f3f;
const int LIM = 1e3 + 13;
int n, m;
int a[LIM][LIM];
int f[LIM][LIM];
int n2[LIM][LIM];
int n5[LIM][LIM];
Pack solve_0()
{
memset(f, -1, sizeof(f));
f[n][m] = !a[n][m];
deque<Info> S;
S.push_back(Info(n, m, f[n][m]));
while (S.size())
{
/// Take current path
int x = S.front().x;
int y = S.front().y;
int d = S.front().d;
S.pop_front();
if (d < f[x][y]) continue; /// Not optimal
for (int k = 0; k < 2; ++k)
{
int u = x - mx[k];
int v = y - my[k];
if (u < 1 || u > n) continue; /// Out of row
if (v < 1 || v > m) continue; /// Out of col
int g = f[x][y] || !a[u][v];
if (f[u][v] < g) /// Take part with a[i][j] = 0
{
f[u][v] = g;
S.push_back(Info(u, v, f[u][v]));
}
}
}
/// Trace back [1][1] -> [n][m]
bool ok = false;
int res = f[1][1];
string way(n + m - 2, 'z');
if (res == 0) return Pack(+INF, way);
for (int x = 1, y = 1, p = 0; p < way.size(); ++p)
{
if (x == n) /// Top row
{
while (p < way.size()) way[p++] = 'L';
break;
}
if (y == m) /// Top column
{
while (p < way.size()) way[p++] = 'D';
break;
}
/// Priority contain a[i][j] = 0 -> Priority f[][] contain a[i][j] = 0 -> 'D'
ok |= !a[x][y];
if (ok || f[x + 1][y] >= f[x][y + 1])
{
way[p] = 'D';
++x;
}
else
{
way[p] = 'L';
++y;
}
}
return Pack(res, way);
}
Pack solve(int a[LIM][LIM])
{
/// Init DP
memset(f, +INF, sizeof(f));
f[n][m] = a[n][m];
deque<Info> S;
S.push_back(Info(n, m, f[n][m]));
while (S.size())
{
/// Take current path
int x = S.front().x;
int y = S.front().y;
int d = S.front().d;
S.pop_front();
if (d > f[x][y]) continue; /// Not optimal
for (int k = 0; k < 2; ++k)
{
int u = x - mx[k];
int v = y - my[k];
if (u < 1 || u > n) continue; /// Out of row
if (v < 1 || v > m) continue; /// Out of col
int g = f[x][y] + a[u][v];
if (f[u][v] > g) /// Take smaller path
{
f[u][v] = g;
S.push_back(Info(u, v, f[u][v]));
}
}
}
/// Trace back [1][1] -> [n][m]
int res = f[1][1];
string way(n + m - 2, 'z');
for (int x = 1, y = 1, p = 0; p < way.size(); ++p)
{
if (x == n) /// Top row
{
while (p < way.size()) way[p++] = 'L';
break;
}
if (y == m) /// Top column
{
while (p < way.size()) way[p++] = 'D';
break;
}
/// Priority Smaller f[][] -> Priority 'D'
if (f[x + 1][y] <= f[x][y + 1])
{
way[p] = 'D';
++x;
}
else
{
way[p] = 'L';
++y;
}
}
return Pack(res, way);
}
int main()
{
/// FASTIO
ios::sync_with_stdio(NULL);
cin.tie(NULL);
/// Input
cin >> n >> m;
for (int i = 1; i <= n; ++i)
for (int j = 1; j <= m; ++j)
cin >> a[i][j];
/// Construct n2[][], n5[][]
memset(n2, 0, sizeof(n2));
memset(n5, 0, sizeof(n5));
for (int i = 1; i <= n; ++i)
{
for (int j = 1; j <= m; ++j)
{
cin >> a[i][j];
if (a[i][j] == 0)
{
/// We ban the way with zero
n2[i][j] = +INF;
n5[i][j] = +INF;
continue;
}
/// Count factors
for (int t = a[i][j]; t % 2 == 0; t /= 2) ++n2[i][j];
for (int t = a[i][j]; t % 5 == 0; t /= 5) ++n5[i][j];
}
}
Pack res;
res = min(solve_0(), min(solve(n2), solve(n5)));
/// Output
cout << res.res << "\n" << res.way;
return 0;
}
```