题目传送门:
最少操作次数 - 题目 - Online Judge (haizeix.com)https://oj.haizeix.com/problem/511需要贪心,不断贪当次最优解,以获得全局最优解。当次最优解又该怎么考虑呢?
初步设想:k > 1 时,a * k 后 a 的变化量最大,应该多做 * k 的操作
用题目验证:2 >> 4 >> 8 >> 9 >> 10,是 4 步而不是 3 步,出了什么问题?
再次思考:正是因为 * k 后 a 的变化量大,导致 a * k 形成的数列相邻元素差值过大
当 b 处在该数列相邻元素中间时,最后一次 * k 完需要很多次 + 1 才能将 a 修正到 b
+ 1 起到一个修正的作用。先 + 1,再 * k,这一步 + 1 就可以达到修正 pow(k, n) 次的效果,大幅减少最终修正次数
再次验证:2 >> 4 >> 5 >> 10,是 3 步
那么如何界定当次就是 + 1 还是 * k 呢?有没有哪些操作是可以确定的?
再找个例子吧,2 11 2,2 >> 4 >> 5 >> 10 >> 11
我们发现到 11 的这一步一定是 + 1,而不能 * 2,因为 11 是奇数,b % k != 0
可以看出,反推是可以直接确定当次最优操作的,正推比较困难
到这一步了,不难发现,一旦 b % k == 0 && b / k >= a,当次最优解就一定是 / k,否则就 - 1
注意到,题目给出的数据范围不小,当 b % k 余数过大时,需要很多次 - 1 操作,有 TLE 风险
所以我们一次性减去 b % k,一次性修正到位
这样还是有 bug 的,不满足 / k 条件时 b 仍有可能是 k 的倍数
减去 b % k 这一为 0 的数值可能导致死循环,如数据 7 9 9
b / k <= a 且 b % k == 0 时,b 变到 a,只能通过 - 1 了,操作次数 += b - a,b = a 就结束了
还要注意 k == 0 || k == 1 的情况,这种情况只能 - 1
代码:
#include <iostream>
#define ll long long
using namespace std;
int main()
{
ll a, b, k, c = 0;
cin >> a >> b >> k;
if (k <= 1) cout << b - a;
else
{
do
{
if (b % k == 0 && b / k >= a) (b /= k), c++;
else
{
if (b % k) c += b % k, b -= b % k;
else c += b - a, b = a;
}
} while (a != b);
cout << c;
}
return 0;
}