(dfs+dp+思维)
题意:给出一个数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; }