佳佳的筷子
题目描述
佳佳与常人不同,吃饭用三只筷子,两根短的加一根比较长的。两只短的筷子的长度应该尽可能接近,但是最长的那根长度是无所谓的。如果一套筷子的长度分别是a,b,c(a<=b<=c),则用(a-b)*(a-b)的值表示这套筷子的质量,这个值越小,这套筷子的质量越高。
佳佳请朋友吃饭,并准备为每人准备一套这种特殊的筷子。佳佳有n(n<=5000)只筷子,他希望找到一种办法搭配好k套筷子,使得这些筷子的质量之和最小。
关于输入
第一行是两个整数n和k(n<=5000,3*k<=n)
第二行是n个整数表示筷子的长度
关于输出
输出一个整数,表示筷子质量和的最小值
例子输入
5 1 1 3 4 7 10
例子输出
1
提示信息
从小到大排序后从后向前递推
解题分析
本题明显地需要动态规划来寻找最佳的解决方案,我们不妨令dp[i][j],表示取i只筷子,拿j套筷子质量和的最小值。拿了两只筷子后,必须还要一只比这两只筷子都长的筷子,所以逆推相对来说较为轻松,并且相邻两数平方和最小,故我们先将筷子按从大到小排序,然后再规定初始条件,对于那些筷子数不足以凑足需要的筷子套数的情况,我们将其状态置为一个较大的数,这样就可以防止错取,对于取0套筷子的情况,因为一套筷子也没有,故质量和置为0。
这段程序使用动态规划算法来解决"佳佳的筷子"问题。
读取输入的整数n和k,表示筷子的数量和需要搭配的套数。
接下来,程序读取n个整数,表示每根筷子的长度,并将它们存储在数组chopsticks中。
然后,程序对筷子的长度进行降序排序,以便后续计算。
接着,程序初始化一个二维数组dp,用于存储中间计算结果。dp[i][j]表示使用前i根筷子搭配j套筷子时的质量之和的最小值。
接下来,程序设置初始条件。对于一些不可能存在的情况,如2只筷子搭配1套筷子dp[2][1],初始化为一个较大的值1e6(表示无穷大),以确保它不会被选中。
然后,程序计算dp[3][1],即使用前3根筷子搭配1套筷子时的质量之和的最小值。这里使用了题目中给出的质量计算公式:(a-b)*(a-b)。
接下来,程序使用动态规划的思想,从小到大依次计算dp[i][j]的值。对于每个dp[i][j],有两种选择:
1. 不选第i根筷子,那么dp[i][j] = dp[i-1][j],表示使用前i-1根筷子搭配j套筷子时的质量之和的最小值。
2. 选第i根筷子,那么dp[i][j] = dp[i-2][j-1] + (chopsticks[i]-chopsticks[i-1])*(chopsticks[i]-chopsticks[i-1]),表示使用前i-2根筷子搭配j-1套筷子时的质量之和的最小值加上当前筷子的质量。
最后,程序输出dp[n][k],即使用所有筷子搭配k套筷子时的质量之和的最小值。
总体思路是通过动态规划,从前往后计算每个状态的最优解,最终得到整体的最优解。通过存储中间计算结果,避免了重复计算,提高了效率。
代码实现
#include <iostream>
#include <algorithm>
using namespace std;
int chopsticks[5005];
int dp[5005][5005/3+1];
int main(){
int n,k; cin>>n>>k;
for(int i=1;i<=n;i++){
cin>>chopsticks[i];
}
sort(chopsticks+1,chopsticks+n+1,greater<int>());
for(int i=1;i<=k;i++){
dp[i*3-2][i]=1e6;
dp[i*3-1][i]=1e6;
}
dp[3][1]=(chopsticks[3]-chopsticks[2])*(chopsticks[3]-chopsticks[2]);
for(int j=1;j<=k;j++)
for(int i=3*j;i<=n;i++){
dp[i][j]=min(dp[i-1][j],dp[i-2][j-1]+(chopsticks[i]-chopsticks[i-1])*(chopsticks[i]-chopsticks[i-1]));
}
cout<<dp[n][k]<<endl;
return 0;
}
当然,使用记忆搜索法也是一个不错的选择,代码相对来说更好理解。
首先,定义常量MAXN为5000,并声明变量n、k用于存储输入的筷子数量和需要搭配的套数。
接下来,定义数组chopsticks用于存储筷子的长度,数组dp用于存储中间计算结果。这里使用了long long int类型来存储筷子长度和计算结果,以确保能够处理较大的数值。
然后定义一个辅助函数S,用于计算质量的平方,即S(x) = x*x。
接下来,程序定义了递归函数f,用于计算dp[i][j]的值。函数的参数i表示当前考虑的筷子编号,j表示剩余的套数。函数的返回值是dp[i][j]的值。
在函数f中,首先进行了一些边界条件的判断。如果i等于0,表示没有筷子可供选择,返回一个较大的值1e9。如果dp[i][j]已经计算过,直接返回其值。如果i小于3*j,表示剩余的筷子数量不足以搭配j套筷子,返回一个较大的值1e9。如果j等于0,表示已经搭配完成,返回0。
然后,在函数f中,使用递归的方式计算dp[i][j]的值。有两种选择:
- 不选第i根筷子,那么dp[i][j] = f(i-1,j),表示使用前i-1根筷子搭配j套筷子时的质量之和的最小值。
- 选第i根筷子,那么dp[i][j] = f(i-2,j-1) + S(chopsticks[i]-chopsticks[i-1]),表示使用前i-2根筷子搭配j-1套筷子时的质量之和的最小值加上当前筷子的质量。
最后,在主函数main中,程序读取输入的n和k,以及每根筷子的长度,并进行降序排序。然后,初始化dp数组为-1,表示尚未计算过。
接下来,调用函数f(n,k)计算筷子质量之和的最小值,并将结果存储在result变量中。
最后,程序输出result,即筷子质量之和的最小值。
#include <iostream>
#include <algorithm>
#define MAXN 5005
using namespace std;
int n,k;
int chopsticks[MAXN]={0},dp[MAXN][MAXN/3+1]={0};
int S(int x){
return x*x;
}
int f(int i,int j){
if(dp[i][j]!=-1) return dp[i][j];
if(i<3*j) return dp[i][j]=1e9;
if(j==0) return dp[i][j]=0;
return dp[i][j]=min(f(i-1,j),f(i-2,j-1)+S(chopsticks[i]-chopsticks[i-1]));
}
int main(){
scanf("%d%d",&n,&k);
for(int i=1;i<=n;i++){
scanf("%d",&chopsticks[i]);
}
sort(chopsticks+1,chopsticks+n+1,greater<int>());
for(int i=1;i<=n;i++)
for(int j=1;j<=n/3+1;j++){
dp[i][j]=-1;
}
int result=f(n,k);
printf("%d\n",result);
return 0;
}