题目链接:https://vjudge.net/problem/UVALive-6434
题意:给你n个数字,要你把这n个数字分成m组,每一组的消耗值定义为改组最大值和最小值之差,要求这m组的消耗值总和最小。
思路:既然我们要求把它分成m组,并且各组的最大值和最小值之差的和最小,我们就可以先把它分成n组,这时的总消耗值为零,我们为了让它的值尽可能的小,我们就可以每次选择两组使得总消耗值增加最少的
两组合并在一起,这时就只有n-1组了,重复以上操作n-m次,就可以得到分成m组所需的最小消耗了。(每次找寻使得总消耗增加最少的两组时,我们只要找到相邻的两组最大值和最小值差最小的就可以了)
假设当前 分组为(3,4)(7,8)
当我们要把这两组和并的时候,我们只要把总消耗值加上第二组的最小值和第一组的最大值的差就可以了,为什么可以这样呢?(当求一组数的最大值和最小值之差的和,不就可以看作是要把这组中的所有数字按照顺序连接在一起,每次连接的消耗
等于每两个相邻数字的差值,总和就是把他们全加其起来,例:(1,6,4,5,7,13,9),他们的最大值和最小值之差为12,不就等于先排序后(1,4,5,6,7,9,13),(4-1)+(5-4)+(6-5)+(7-6)+(9-7)+(13-9)=12 ,对于每组的连接也是相似的,只要把组看作单个数字就可以了
,只是它对于它前面一组的值是本组的最小值,对于后面一组的值为本组的最大值 )
我们可以看作把3,4和成一块消耗值为1,把7,8和成一块消耗值为1,这时3,4可以看作是一块,7,8可以看作是一块,当你要把3,4和7,8这两块连接在一起的时候,要增加的消耗不就是把4和7连接在一起的消耗吗。
#include<cstdio>
#include<algorithm>
#include<cstring>
#define INF 1e9+5
using namespace std;
int s[110];
int vt[110];//记录当前位置的数字是属于哪个集合的
int main(){
int t;
int n,m;
int i,j,k;
int a,b,c;
int mn;
int nb=0;
scanf("%d",&t);
while(t--){
nb++;
scanf("%d%d",&n,&m);
for(i=0;i<n;i++){
scanf("%d",&s[i]);
vt[i]=i;//分成n组
}
if(m==n)
printf("Case #%d: 0\n",nb);
else{
sort(s,s+n);
for(i=n;i>m;i--){
mn=INF;
a=vt[0];
for(j=1;j<n;j++){
if(vt[j]!=a){//两个分组的交界位置
if(s[j]-s[j-1]<=mn){//找把两个集合合并在一起增加消耗最小的位置 ,一开始的思路只是找出合并后最大值和最小值之差最小的两个组进行合并
//结果wa了一发,仔细想想后发现这样合并后并不能把增加的消耗值降到最小
/*
1
10 2
1 5 6 7 8 9 10 13 13 13
当分组数位3的时候
此时分组为 (1) (5,6,7,8,9,10) (13,13,13)
如果只是单纯的把合并后最大值和最小值之差最小的两组合并的话,
(1,5,6,7,8,9,10) (13,13,13)
此时总消耗为9
(1) (5,6,7,8,9,10,13,13,13)
而最小的总消耗却是8
不能保证每次的增长量最小
*/
c=j;//记录分界点
mn=s[j]-s[j-1];
}
a=vt[j];//连接相邻的两个组的消耗一定是最小的
}
}
/*for(j=0;j<n;j++){//不太理解的同学可以输出一下每次的分组情况便于理解
printf("%d ",vt[j]);
}
printf("\n");*/
int y=vt[c];
for(j=c;vt[j]==y;j++){//合并
vt[j]=vt[c-1];
}
}
a=vt[0];
b=0;
long long sum=0;
for(i=1;i<n;i++){
if(vt[i]!=a){
sum+=s[i-1]-s[b];//计算消耗值
b=i;
a=vt[i];
}
}
sum+=s[n-1]-s[b];
printf("Case #%d: %lld\n",nb,sum);
}
}
return 0;
}
#include<stdio>
#include<algorithm>
using namespace std;
int s[110]
int main(){
int t,n,m;
int i,j,nb;
nb=0;
int a,b;
scanf("%d",&t);
while(t--){
nb++;
scanf("%d%d",&n,&m);
for(i=0;i<n;i++){
scanf("%d",&s[i]);
}
sort(s,s+n);
for(i=0;i<n-1;i++)//直接记录下所有相邻的数字要合并在一起的消耗(这和上个代码的核心思想一致,但是
//少了许多不必要的操作,直接记录下最核心的数据)
s[i]=s[i+1]-s[i];
sort(s,s+n-1);
int sum=0;
for(i=0;i<n-m;i++){//每次找到消耗代价最小的那一次合并
sum+=s[i];
}
printf("Case #%d: %d",nb,sum);
}
return 0;
}