题意:给出一个数n,每次操作可以减去这个数位数上的某一位,问你最小多少次操作可以将n减为0.
分析:对于C1的弱数据直接O(1)实现即可(dp预处理出所有结果),C2和C3我是直接做C3的,对于n为1e18的情况,我们预处理肯定是不可能的,当是我们打表也可以发现在递减的过程中重复数据是很多的,因此可以使用map进行记忆化存储中间某些过程答案,使重复的操作只执行一次,之后O(logn)读取数据即可(尽管写的是O(logn),但是实际上里面的n超不过1e4)
题解: 这里我们要如何充分利用到重复的数据呢,可以考虑用拆位的方式,将低位数据取出来dfs算(主要利用记忆化读取的地方),再将结果加到高位进行dfs计算总的结果,具体拆位实现eg:将24拆分成先算4的结果再算20的结果,同时我们还需要考虑的就是对于同样一个低位数据时,可能最大能减的数不同,因此我们需要保存的是pair<最大能减的数,当前值>,而记忆化的到的结果是pair<最小操作数,余数>因为考虑到eg:98=》89而不是90更优的情况,因此在计算低位的结果后需要存在一个余数,高位计算时就先加上这个余数,上面98的情况拆分为90和8,低位dfs(pair<9,8>)得到的余数应该为-1,之后dfs高位时就应该是dfs(pair<0,90+(-1)>)(具体看代码即可)
代码如下:
#include<iostream>
#include<cmath>
#include<string>
#include<cstring>
#include<cstdio>
#include<time.h>
#include<algorithm>
#include<vector>
#include<map>
using namespace std;
#define inf 0x3f3f3f3f
#define ll long long
#define x first
#define y second
#define mk make_pair
typedef pair<ll, ll>pr;
map<pr, pr>dp;//map<pr<最大能减的数,当前值>,pr<最小操作数,余数> >dp
pr dfs(pr a) {
if (a.y < 10) //10以内返回最小操作数以及对高位的影响数值a.y(可能为负数)
return mk(a.x || a.y, a.y - max(a.x, a.y));
if (dp.find(a) != dp.end())//若是找到记忆化数据直接读取
return dp[a];
ll p = 1;
while (p <= a.y / 10)//获得与a.y同位的最小值(因为a.y<10的以及先return了,因此不会受到影响)
p *= 10;
//下面将进行拆数计算
pr b = dfs(mk(max(a.x, a.y / p), a.y%p));//低位计算
ll ans = b.x;//将低位的操作数加上
b = dfs(mk(a.x, a.y / p*p + b.y));//高位计算
return dp[a] = mk(b.x + ans, b.y);
}
int main() {
ll n;
scanf("%lld", &n);
printf("%lld\n", dfs(mk(0, n)).x);
return 0;
}