题意:这里有n个同学在你的大学,第i个同学的编程技巧是ai,作为一个教练,你想把它们分成很多组。每组至少3个人。
求每组差异的值的总和。如何分组,才能使得差异值的总和最小。
分析:先排序,肯定是连续数字的组中最大值和最小值差值最小。然后考虑分组的人数的上限,我们最多一组5个人,
因为假设有6个人,差值为a6 - a1,我们可以分成更多的组数,使得差值减小,比如a3 - a1和a6 - a4,这两组的差值加起来
等于a6 - a1 + a3 - a4,a3 - a4 < 0,因此答案会变小。
递推方程为 f[i + j] = min(f[i + j], f[i] + a[i + j].val - a[i + 1].val);
然后这道题要求我们输出具体的分组方案,求具体的分组方案,我们可以记录每个状态从哪个状态转移过来,然后从最后一个状态反推。
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
using LL = long long;
const int N = 200005;
//LL a[N];
LL f[N];//处理前i个人,得到的最小差值
int p[N];//组编号
int res[N];
struct Node
{
LL val;
int id;//编号
bool operator<(const Node& rhs) const
{
if (val == rhs.val)
return id < rhs.id;
return val < rhs.val;
}
}a[N];
int main()
{
memset(f, 0x3f, sizeof f);
int n;
scanf("%d", &n);
for (int i = 1; i <= n; ++i)
{
scanf("%lld", &a[i].val);
a[i].id = i;//编号
}
sort(a + 1, a + n + 1);
f[0] = 0;
for (int i = 0; i <= n; ++i)
{
for (int j = 3; j <= 5 && i + j <= n; ++j)
{
LL diff = a[i + j].val - a[i + 1].val;
if (i + j <= n && f[i + j] > f[i] + diff)
{
f[i + j] = f[i] + diff;
p[i + j] = i;//从i转移过来
}
}
}
int cur = n;
int cnt = 0;//组数
while (cur != 0)
{
for (int i = cur; i >= p[cur] + 1; --i)
{
res[a[i].id] = cnt;
}
++cnt;
cur = p[cur];
}
cout << f[n] << " " << cnt << endl;
for (int i = 1; i <= n; ++i)
printf("%d ", res[i] + 1);
return 0;
}