首先我们都知道 10 = 5 × 2 10=5\times 2 10=5×2。所以我们只需要去用 DP 来解决此题。
先来讲一个典型的错误。可能就我犯过这个错误/kk
我们如果定义
f
i
,
j
f_{i,j}
fi,j 为从
(
1
,
1
)
(1,1)
(1,1) 到
(
i
,
j
)
(i,j)
(i,j) 的路上因子
2
2
2 和因子
5
5
5 的个数的最小值的最小值,那么我们就走上了一条不归路像我一样。我们看下面这组数据:
2
1 125
10 8
如果按照我们上面的定义,那么我们的路就选择了 ( 1 , 1 ) − > ( 1 , 2 ) − > ( 2 , 2 ) (1,1)->(1,2)->(2,2) (1,1)−>(1,2)−>(2,2),末尾有三个 0 0 0。而我们选择 ( 1 , 1 ) − > ( 2 , 1 ) − > ( 2 , 2 ) (1,1)->(2,1)->(2,2) (1,1)−>(2,1)−>(2,2),末尾有只有两个 0 0 0。所以答案错误。
正解
我们换一个定义方式:我们定义 f i , j f_{i,j} fi,j 为从 ( 1 , 1 ) (1,1) (1,1) 到 ( i , j ) (i,j) (i,j) 的路上因子 2 2 2 的个数的最小值。我们可以得到转移方程, calc ( x ) \operatorname{calc}(x) calc(x) 表示 x x x 中所含因子 2 2 2 的个数:
{ f i , j = f i − 1 , j + calc ( x ) f i − 1 , j ≤ f i , j − 1 f i , j = f i , j − 1 + calc ( x ) f i − 1 , j > f i , j − 1 \begin{cases} f_{i,j}=f_{i-1,j}+\operatorname{calc}(x)&f_{i-1,j\leq f_{i,j-1}}\\ f_{i,j}=f_{i,j-1}+\operatorname{calc}(x)&f_{i-1,j> f_{i,j-1}} \end{cases} {fi,j=fi−1,j+calc(x)fi,j=fi,j−1+calc(x)fi−1,j≤fi,j−1fi−1,j>fi,j−1
因子 5 5 5 计算方式相同,这里不再赘述。
最后的答案是 f n , n f_{n,n} fn,n 计算出的因子 5 5 5 的个数和因子 2 2 2 的个数的最小值。因为我们完全可以让 5 5 5 去顺应 2 2 2。因为没有 2 2 2,再多 5 5 5 也没有用处。如果同时然 2 , 5 2,5 2,5 最小,也就是上面的错误示例,一定是得不到正确答案的。
路径的处理就可以在 DP 是就处理好,最后倒退一遍即可。
慢着,我们还有一个非常重要的事情没有讲,就是 0 0 0 的特判。
如果路线上含有 0 0 0 那么答案一定为 1 1 1。比如这一组数据:
8 125 24
2025 12 12
0 56 425
我们按照上南面的方法计算,我们会得到答案 3 3 3。然而,我们因为有 0 0 0,所以答案一定为 0 0 0。我们需要特殊处理路径。注意边界
#include<bits/stdc++.h>
#define int long long
using namespace std;
int read() {
char ch=getchar();
int f=1,x=0;
while(ch<'0'||ch>'9') {
if(ch=='-')
f=-1;
ch=getchar();
}
while(ch>='0'&&ch<='9') {
x=x*10+ch-'0';
ch=getchar();
}
return f*x;
}
const int maxn=1001;
int n,a[maxn][maxn],zx,zy;
bool zero;
char ans[maxn<<1];
struct pr {
int two,five;
int dt,df;
}f[maxn][maxn];
pr calc(int x) {
pr ret;
ret.five=ret.two=0;
if(x==0) {
return ret;//0要特判
}
while(x%2==0) {
ret.two++;
x>>=1;
}
while(x%5==0) {
ret.five++;
x/=5;
}
return ret;
}
signed main() {
n=read();
for(int i=1;i<=n;i++) {
for(int j=1;j<=n;j++) {
a[i][j]=read();
if(!a[i][j]) {
zero=1;
zx=i;
zy=j;
}
}
}
for(int i=2;i<=n;i++) {
f[0][i].five=f[0][i].two=f[i][0].five=f[i][0].two=0x7fffffff;
}
for(int i=1;i<=n;i++) {
for(int j=1;j<=n;j++) {
pr tmp=calc(a[i][j]);
if(f[i-1][j].two<f[i][j-1].two) {
f[i][j].two=f[i-1][j].two+tmp.two;
f[i][j].dt='D';//可以同时维护路径
}
else {
f[i][j].two=f[i][j-1].two+tmp.two;
f[i][j].dt='R';
}
if(f[i-1][j].five<f[i][j-1].five) {
f[i][j].five=f[i-1][j].five+tmp.five;
f[i][j].df='D';
}
else {
f[i][j].five=f[i][j-1].five+tmp.five;
f[i][j].df='R';
}
}
}
if(zero&&min(f[n][n].five,f[n][n].two)>1) {//0要特判
cout<<1<<endl;
for(int i=1;i<zy;i++) {
cout<<'R';
}
for(int i=1;i<n;i++) {//注意边界哦~
cout<<'D';
}
for(int i=zy+1;i<=n;i++) {
cout<<'R';
}
return 0;
}
int t;
if(f[n][n].five<f[n][n].two) {
t=1;
cout<<f[n][n].five<<endl;
}
else {
t=2;
cout<<f[n][n].two<<endl;
}
int x=n,y=n,cnt=0;
if(t==1) {
while(!(x==1&&y==1)) {
ans[++cnt]=f[x][y].df;
if(f[x][y].df=='D') {
x--;
}
else {
y--;
}
}
}
else {
while(!(x==1&&y==1)) {
ans[++cnt]=f[x][y].dt;
if(f[x][y].dt=='D') {
x--;
}
else {
y--;
}
}
}
for(int i=cnt;i;i--) {
cout<<ans[i];
}
cout<<endl;
return 0;
}