题目描述:
有n个学生,学生的算法水平为a1,a2,a3…,an,现在把他们分成k个队伍,每个队伍至少有3个人,定义极差为每个队伍中最高水平和最低水平之差,求如何分队才能使所有队伍极差总和最小。
输入:
学生人数n(3<=n<=2e5)
学生水平a1,a2,a3…,an(1<=ai<=1e9)
输出:
最小极差总和res,最优队伍数k
t1,t2,t3…,tn,ti表示第i个学生的队伍编号
Solution:
为了便于求最小极差和,我们先将ai进行有小到大排序。
由于题目要求,队伍人数不能小于3个人,故当n为1~5时,队伍数都为1。而当n=6时,我们有两种选择,一种是只分一队,第二种是分两支3人队,而当我们讲这两种情况的极差和进行对于,由于ai递增,我们会发现a6-a4+a3-a1>=a6-a4+a4-a1=a6-a1,显然分成两队所得的极差和一定不会比一队的差。
由此我们可以推出当一支队伍能分至不可分时才能得到最小极差和,但是由于一个队伍可以有3,4,5人3种人数(例如:n=9时,我们并不知道分成3队人数分别为3,3,3更优,或者是2队人数分别为4,5更优),故我们采用dp的方式来得到最终答案。
对于dp[i]的状态表示,我们定义为前i个人分队时的最小极差和。
由以上分析,我们只要去枚举对于当前第i个人组成的队伍人数为3,4,5人
便能得到转移方程为dp[i]=min(dp[i],dp[j]+a[i]-a[j+1]),i-5<=j<=i-3。
对于最后的具体方案,我们只要简单的双指针去判断dp[n]由哪个状态转移而来的便能得到最终答案。
C++code:
#include <bits/stdc++.h>
using namespace std;
const int N = 2e5 + 10;
int n;
int t[N], dp[N];
struct mys {
int a, p;
bool operator<(const mys& W)const {
return a < W.a;
}
}m[N];
int main() {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
memset(dp, 0x3f, sizeof dp);//初始化dp数组
dp[0] = 0;
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> m[i].a;
m[i].p = i;
}
sort(m + 1, m + 1 + n);
for (int i = 3; i <= n; i++) {//dp转移过程
for (int j = max(0, i - 5); j <= max(0, i - 3); j++) {
dp[i] = min(dp[i], dp[j] + m[i].a - m[j + 1].a);
}
}
int i = n, j = n - 3, team = 1;//求总队数以及每个人对应的队伍编号
while (j >= 0) {
if (dp[i] == dp[j] + m[i].a - m[j + 1].a) {
for (int k = j + 1; k <= i; k++)t[m[k].p] = team;
i = j, j = i - 3;
team++;
}
else j--;
}
cout << dp[n] << ' ' << team - 1 << endl;
for (int i = 1; i <= n; i++)cout << t[i] << ' ';
return 0;
}