如何将一个字符以最少的按键次数复制为N个字符.
算法思路:这是一个动态规划的问题,首先我们需要思考能做出的决策.[1]按键一次,输入一个字[2]全选[3]复制[4]粘贴.如果要将原来的字符变成2份.需要:[2][3][4][4],粘贴2次才能变成2倍.如果继续粘贴,我们需要k次按键将其复制为k-2倍.
现在考虑如何写出递归表达式.假设F[n]代表复制到n个字符需要的时间,那么有哪些途径可以到达F[n]的状态呢?以n=10为例进行探索.我们可以有1-〉2-〉4-〉8-〉9-〉10的途径.可以有1-〉2-〉4-〉6-〉8-〉10,还可以1-〉2-〉4-〉5-〉10,也可以1-〉2-〉3-〉6-〉9-〉1等等办法.现在我们需要考虑的是如何归纳这些途径.
我们考虑最接近F[n]的时候,如果字符小于n/2,那么总可以通过复制粘贴进入大于n/2的状态.如果等于n/2,只需4次+F[n/2]即可.如果大于n/2,这个时候继续全部复制是不可以的,会超过n的字数.排出一些明显不划算的结论,那么就有两个选择,(0):利用当前粘贴板已有的内容继续粘贴。(6->8->10)(1)引入其他操作使得状态到达n-1(8->9->10)。总之如果进入大于n/2的状态后不能通过反复粘贴达到n的话,就必须经过n-1的状态.
综上F[n] = min{F[n-1]+1,F[n/k]+k+2}//k是n的约数.
#include "iostream"
#include "vector"
#include "algorithm"
using namespace std;
class Divisor
{
friend int get_min_number(const Divisor&);
public:
Divisor(size_t n) :N(n), divisor{n+1}
{
divisor[1] = { 1 };
for (size_t i = 1; i != N / 2+1; ++i)
{
for (size_t k = 2; k*i <= N;++k)
divisor[i * k].push_back(i);
}
}
private:
size_t N = 0;
vector<vector<size_t>> divisor;
};
int get_min_number(const Divisor& data)
{
int N = data.N;
vector<size_t> F(N+1);
F[1] = 1;
for (size_t i = 2; i != N + 1; ++i)
{
F[i] = data.N;
for (auto k : data.divisor[i])
F[i] = min<size_t>(F[i],min<size_t>(F[i - 1] + 1, F[i / k] + k + 2));
}
return F[N];
}
int main()
{
Divisor d(100);
auto result = get_min_number(d);
return 0;
}