假设你有一个特殊的键盘,键盘上有如下键:
键1
: (A): 在屏幕上打印一个'A'。键2
: (Ctrl-A): 选择整个屏幕。键3
: (Ctrl-C): 复制选择到缓冲区。键4
: (Ctrl-V): 在屏幕上已有的内容后面追加打印缓冲区的内容。现在,你只能按键盘上N次(使用以上四个键),找出你可以在屏幕上打印的“A”的最大数量
第一种思路:dfs暴力枚举法
我们设置一个dfs递归,他的形参设置为dfs(剩余操作次数,屏幕上a的数字,剪贴板)
dfs(N,a_num,copy)
会发生以下3种操作
如果需要 直接增加A dfs(N-1,a_num+1,copy)
由于全选和复制是一起发生的 dfs(N-2,a_num,a_num)
粘贴的操作 dfs(N-1,a_num+copy,copy)
代码如下
#include <iostream>
#include <algorithm>
using namespace std;
int ans = 0;
void dfs(int N, int a_num, int copy)
{
if (N == 0)
{
ans =max(ans, a_num);
return;
}
dfs(N - 1, a_num + 1, copy);
dfs(N - 1, a_num + copy, copy);
if (N >= 2)
dfs(N - 2, a_num, a_num);
}
int main()
{
int N;
cin >> N;
dfs(N, 0, 0);
cout << ans;
}
这样子实际上通过枚举来解决该题的,枚举所有的操作,最后得出屏幕上获得的最多的A字符,由于一共有3种操作,实际上,时间复杂度是很接近于O(3的n次方的),所以我们需要剪枝
记忆化搜索
#include <iostream>
#include <algorithm>
using namespace std;
int ans = 0;
int dp[1002][1002][100];
int MAX(int a, int b, int c)
{
int x;
a > b ? x = a : x = b;
x > c ? x : x = c;
return x;
}
int dfs(int N, int a_num, int copy)
{
if (N <= 0)
{
return a_num;
}
if (dp[N][a_num][copy])
return dp[N][a_num][copy];
return dp[N][a_num][copy] = MAX(dfs(N - 1, a_num + 1, copy)
, dfs(N - 1, a_num + copy, copy),
dfs(N - 2, a_num, a_num));
}
int main()
{
int N;
cin >> N;
cout<<dfs(N, 0, 0);
}
记忆化搜索可以达到一定剪枝的效果,但是诚然,这是完全不够的,我们可以假设一下,在这个算法里,会出现许多 ctrl+c ctrl+a 连续多次的情况,或者没有a就在复制粘贴,这显然是不会得到最多a字符的,我们就需要避免这种情况的发生
第二种思路:dp动态规划
我们知道,出现最多A字符的情况只会发生在 AAA...复制粘贴(1次或多次)...复制粘贴...AAA...
于是,最后一次的操作,要么是输入A,要么是粘贴,这样子就可以确保在屏幕上出现的A字符是最多的,于是我们需要在一个i的时候,枚举要发生多少次的粘贴,然后用他与输入A作比较,看看这个时候在屏幕上的A数目在选择哪个操作时是最优的
#include <iostream>
#include <algorithm>
using namespace std;
int ans = 0;
int dp[1002];
int main()
{
int N;
cin >> N;
for (int i = 1;i <= N;i++)
{
dp[i] = dp[i - 1] + 1; //输入一个A
//依次枚举发生了i-j次粘贴的情况
//dp[j-2]表示全选复制时的剪贴板,(i-j+1)中i-j表示粘贴次数,1表示的是dp[j-2]就在屏幕上的A个数
for (int j = 2;j < i;j++)
{
dp[i] = max(dp[i], dp[j - 2] * (i - j + 1));
}
}
cout << dp[N];
}