12 佳佳的筷子
描述
佳佳与常人不同,吃饭用三只筷子,两根短的加一根比较长的。两只短的筷子的长度应该尽可能接近,但是最长的那根长度是无所谓的。如果一套筷子的长度分别是a,b,c
(
a
<
=
b
<
=
c
)
(a<=b<=c)
(a<=b<=c),则用
(
a
−
b
)
∗
(
a
−
b
)
(a-b)* (a-b)
(a−b)∗(a−b)的值表示这套筷子的质量,这个值越小,这套筷子的质量越高。
佳佳请朋友吃饭,并准备为每人准备一套这种特殊的筷子。佳佳有n
(
n
<
=
5000
)
(n<=5000)
(n<=5000)只筷子,他希望找到一种办法搭配好k套筷子,使得这些筷子的质量值和最小.
关于输入
第一行是两个整数n和k
(
n
<
=
5000
,
3
∗
k
<
=
n
)
(n<=5000,3*k<=n)
(n<=5000,3∗k<=n)
第二行是n个整数表示筷子的长度
关于输出
输出一个整数,表示筷子质量和的最小值
例子输入
5 1
1 3 4 7 10
例子输出
1
提示
从小到大排序后从后向前递推
分析
根据平方和的性质我们不难分析出,首先我们把筷子长度序列排好序,然后要挑选的筷子必须是相邻的两根,隔着挑肯定没有直接挑好。从这一点看,本题似乎就直接变成了 在一个排序序列中找出连续的 k组数,使得这 k组数的和最小 这个问题。然而事实上却不是这样,因为题目还要求要有第三根筷子,尽管这根筷子不在优良计算范围之内,但是它必须存在,否则这种选择的情况就不符合题意。
为了解决上述问题,我们可以把序列颠倒,使得大数在前小数在后。这样,当我们用这个序列向后递推时,如果用递推公式更新了dp[i][j]
的值,那么就表示在[i][j]
后面我们又重新选择了新的筷子组,那么之前的筷子就可以作为第三根长筷子,就一定保证了第三根筷子的存在。
本题显然不能用递归来完成,巨大的时间复杂度绝对会超时。然而尝试之后发现,记忆化搜索也超时了,故而不得不使用递推来完成本题。之所以记忆化搜索会超时,是因为这个题目实在是太复杂了,如果所有情况都遍历的化,哪怕是记忆化搜索降低了重复运算消耗,在时间上来说也太慢了,多次递归的调用损耗太大了。并且在递推中我们还可以发现一些便捷边界,用于减少运算量
本题的递推公式其实并不难,从这类递推问题的共性中我们甚至不难发现,递推公式就是一个对它自己原本的某种状态和做某种变更之后的状态进行的一个取极值。本题递推公式为:函数中的两项分别表示对 i − 1 i-1 i−1根筷子的两种决策:选择让其加入筷子组,或者不加入。不加入,就不变 j j j。
dp[i][j] = min(dp[i - 1][j], dp[i - 2][j - 1] + (len[i] - len[i - 1]) * (len[i] - len[i - 1]))
本题的边界状态也是一个值得深思的问题。
- j = 0 j=0 j=0,表示当前需要对0组筷子进行组合,无论 i i i为多少,对应的值是0
- i > n i>n i>n,表示需要更多的筷子来组合,但是我只有这么多筷子,故而这种情况是不行的,不存在的情况返回 ∞ ∞ ∞
#include<iostream>
#include<algorithm>
using namespace std;
int n, k;
int arr[5001];
int dp[5001][5000 / 3 + 1];
bool cmp(int a, int b)
{
return a > b;
}
inline int min(int a, int b)
{
return a < b ? a : b;
}
int main()
{
cin >> n >> k;
for (int i = 0; i < n; ++i)
cin >> arr[i];
sort(arr, arr + n, cmp);
for (int i = 1; i <= k; ++i)//置初值,筷子不够的情况直接毙掉
dp[3 * i - 1][i] = 99999, dp[3 * i - 2][i] = 99999;
for (int c = 1; c <= k; ++c)
{//最外层是凑多少双筷子
for (int t = 3 * c; t <= n; ++t)
{//内层是用多少只筷子凑筷子,这样可以节约计算资源
dp[t][c] = min(dp[t - 1][c], dp[t - 2][c - 1] + (arr[t - 1] - arr[t - 2]) * (arr[t - 1] - arr[t - 2]));
}
}
cout << dp[n][k];
}