AcWing 549训练
题目描述:
作为一名学校足球教练,你的任务是挑选一支由 P 个学生组成的团队代表你的学校。
共有 N名学生供你挑选,第 i 名学生的技术等级为 Si,这是一个正整数,表示他们的技术水平。
在你看来一个合理的团队中的 P个球员的技术应该是相当的,这样才能使每个人都融入到队内。
在最开始,你可能无法直接选出一个配置合理的队伍,因此你将为一些学生提供一对一的辅导。
将一名学生的技术等级提高 1需要你花费 1个小时的时间来进行辅导。
比赛季很快就开始了(事实上,第一场比赛已经开始了!),所以你想知道训练出一个合理的团队,你需要提供的最少训练小时数是多少。
输入格式:
第一行包含整数 T,表示共有 T 组测试数据。每组测试数据的第一行包含两个整数 N和 P。
第二行包含 N个整数 Si,其中第 i个整数为第 i个学生的技术等级。
示例:
3
4 3
3 1 9 100
6 2
5 5 1 2 3 4
5 5
7 7 1 7 7
输出样例:
Case #1: 14
Case #2: 0
Case #3: 6
算法思想:
本质上是求从n个数中挑选p个数使得其之间与最大者的差最小,所以首先对数组排序,问题转变为数组上p个连续数与p个数中最大值之间的差最小的值。
1、暴力枚举:
对数组遍历,每次枚举i和i + p-1之间的数与a[i + p - 1]之间差的和,变量minValue记录枚举过程中的最小值,数组遍历完毕后返回最小值即可。
c++代码实现:
#include <iostream>
#include <cstdio>
#include <vector>
#include <algorithm>
using namespace std;
int t, n, p;
int main(){
cin >> t; // 读入t组数据
for(int k = 1; k <= t; k++){
cin >> n >> p; // 每组数据读入n p;
vector<int> a(n,0); // 创建数组a保存读入的n个数
for(int i = 0; i < n; i++) cin >> a[i];
sort(a.begin(),a.end()); // 对数组排序
int minValue = 1e9 + 10,temp = 0; // minValue记录 i 枚举过程中p个数中与a[i + p -1]之间差的和最小值。
for(int i = 0; i + p <= n; i++){
for(int j = i; j < i + p ; j++){
temp += (a[i + p - 1] - a[j]);
}
minValue = min(minValue,temp);// 更新minValue
temp = 0;
}
printf("Case #%d: %d\n",k, minValue);
}
return 0;
}
时间复杂度 O(n^2)
2、前缀和
求p个单调递增的数即a[i + p -1]到a[i]之间与a[i]的差的和,可以转化为p * a[i] - (a[i-p + 1] + a[i-p + 2]+…+a[i])的值所以可以使用前缀和优化a[i-p + 1] 到 a[i]之间的数的和。
c++代码实现:
#include <iostream>
#include <algorithm>
#include <limits.h>
using namespace std;
const int N = 1e5 + 10;
int a[N],sum[N]; // 定义a数组保存每次读入的n个数,sum数组记录a数组的前缀和
int main(){
int t;
cin >> t;
for(int k = 1; k <= t; k ++){
int n, p;
cin >> n >> p;
for(int i = 1; i <= n; i++) cin >> a[i]; // 读入n个数
sort(a + 1,a + 1 + n); // 对 a 数组排序
for(int i = 1; i <= n; i++) sum[i] = sum[i-1] + a[i]; // 构造a的前缀和数组
int ans = INT_MAX;
for(int i = p; i <= n; i++){
ans = min(ans,p * a[i] - (sum[i] - sum[i-p])); // 转化为p个a[i]减去a[i-p+1]到a[i]的和
}
printf("Case #%d: %d\n", k,ans);
}
return 0;
}
时间复杂度:
对数组a排序的时间复杂度是 O(nlogn)。其余都是O(n)