题意:牛可乐有n个元素(编号1...n),第i个元素的能量值为ai。牛可乐可以选择至少k个元素来释放一次魔法,魔法消耗的魔力是这些元素能量值的极差.
形式化地,若所用元素编号集合为S,则消耗的魔力为这个集合中的最大值减最小值。
牛可乐要求每个元素必须被使用恰好一次。牛可乐想知道他最少需要多少魔力才能用完所有元素,请你告诉他。
(链接)[https://ac.nowcoder.com/acm/contest/3003/H]
分析:贪心地思考,要使每次使用的魔力尽量小,必须是一段连续元素的区间,即要从小到大排序,然后,我们再使用动态规划进行转移,f[i]表示用掉前i个元素的最小花费,当我们计算第i个元素的时候,
它肯定是作为最小元素被减去的,然后最大元素是[1,i - k + 1]之间的,我们表示为j,同时还要加上f[j - 1],即用掉前j - 1个元素的最小值,那么状态转移方程就是f[i] = min(f[i], f[j - 1] - a[j] + a[i]) j∈[1, i - k + 1]。
//对于时间复杂度为0(n^2)的DP问题,我们可以采用一些优化,可以利用之前前缀的最小值进行优化,这样,代码的时间复杂度就会降到o(n)。
时间复杂度o(n^2)
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
using LL = long long;
const int N = 3e5 + 7;
int a[N];
//用掉前i个元素的最小代价
int f[N];
int main()
{
int n, k;
scanf("%d%d", &n, &k);
for (int i = 1; i <= n; ++i)
{
scanf("%d", &a[i]);
}
sort(a + 1, a + 1 + n);
for (int i = 1; i <= n; ++i) f[i] = 2e9;
for (int i = k; i <= n; ++i)
{
for (int j = 1; j <= i - k + 1; ++j)
f[i] = min(f[i], f[j - 1] - a[j] + a[i]);
}
cout << f[n] << endl;
return 0;
}
时间复杂度o(n)
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
using LL = long long;
const int N = 3e5 + 7;
int a[N];
//用掉前i个元素的最小代价
int f[N];
int pre;
int main()
{
int n, k;
scanf("%d%d", &n, &k);
for (int i = 1; i <= n; ++i)
{
scanf("%d", &a[i]);
}
sort(a + 1, a + 1 + n);
pre = -a[1];
for (int i = 1; i <= n; ++i) f[i] = 2e9;
for (int i = k; i <= n; ++i)
{
f[i] = pre + a[i];
pre = min(pre, f[i - k + 1] - a[i - k + 2]);
}
cout << f[n] << endl;
return 0;
}