F - n点游戏 [PSA]
24点游戏是非常经典而简单的小游戏,从一堆扑克牌中抽取4张,向其中添加运算符号并使其运行结果恰等于24,这叫作24点游戏。
现在我们不再是组合24,而是组合出给定的数字n,但要求只可以利用任意多个数字1,并且运算只有加法、乘法和括号。对于给定的数字N,最少需要几个1可以完成
数据输入
输入一个数字N( 1≤N≤5000)
数据输出
输出一个整数,表示仅用加号,乘号和括号能组成等于N所需最少的1的个数
样例1
输入样例
7
输出样例
6
提示
7=(1+1)*(1+1+1)+1
样例2
输入样例
27
输出样例
9
提示
27=(1+1+1)*(1+1+1)*(1+1+1)
很明显,这个一个动态规划的题
想要获得一个数的最优解,就要获得组成这个数的最优解。
1:1 2:2 3:3 4:4
5:5 6:5 7:6 8:6
9:6 ... 12:7 13:8
26:10 27:9
设f(n)为数n的最优解
可以发现,质数=f(n-1)+1 合数= f(其组成的原子数) 之和。这里的原子数就是不可再拆分的数==质数。
结合贪心,可以发现,原子数的大小应当尽量的大。(初始值使temp=sqrt(n))即应当是f(9)=f(3)+f(3) 而不是f(2)+f(2)+f(2)+f(2)+1
JAVA版写了一个递归,思想同上,自顶向下的思路,因此要加一个记忆数组,防止出现重复计算。(空间换时间)
需要注意如果N由X * Y组成,那么还需要判断一下f(N-1)+1与f(X)+f(Y)的大小。
import java.util.Scanner;
public class Main {
static int remb[] = new int[5005];
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
remb[1]=1;
remb[2]=2;
remb[3]=3;
System.out.println(f(n));
}
static int f(int n) {
if(remb[n]!=0)
return remb[n];
int temp =(int)(Math.pow(n, 0.5));
while(temp!=1) {
if(n%temp==0) {
remb[n] = f(temp)+f(n/temp);
break;
}
temp--;
}
if(temp==1) {
remb[n] = f(n-1)+1;
}else
remb[n] = Math.min((f(n-1)+1),remb[n]); //重要 有个别测试点需要的
return remb[n];
}
}
也可以是自底向上的思想。
一般的,自顶向下的思维是我们最容易想到的,但需要加记忆数组,代码相对多一点。
而自底向上的思想需要绕一下弯,一般代码较少,更简洁。
两者的运行效率一般都差不多
C++版的自底向上:
#include <cstdio>
#include <algorithm>
#define MAXN 5007
using std::min;
int main(void)
{
int i,j,N;
int dp[MAXN];
scanf("%d",&N);
dp[1]=1;
for(i=2;i<=N;i++)
{
dp[i]=dp[i-1]+1;
for(j=2;j*j<=i;j++)
{
if(i%j==0)
{
dp[i]=min(dp[i],dp[i/j]+dp[j]);
}
}
}
printf("%d\n",dp[N]);
return 0;
}