CodeForces ABBYY Cup 3.0 - Finals C The Great Julya Calendar

题目链接

(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;
}

猜你喜欢

转载自blog.csdn.net/weixin_41156591/article/details/80557054