8597 石子划分问题
时间限制:1000MS 代码长度限制:10KB
提交次数:0 通过次数:0
题型: 编程题 语言: G++;GCC;VC;JAVA
Description
给定n个石子,其重量分别为a1,a2,a3,…,an。
要求将其划分为m份,每一份的划分费用定义为这份石子中最大重量与最小重量差的平方。
总划分费用为m份划分费用之和。
现在对于给定的n个石子,求一种划分方案,使得总划分费用最小。
输入格式
第一行两个正整数n和m,接下来一行有n个正整数,表示一个石子的重量ai。(1≤n, m, ai≤1000)
输出格式
计算输出最小总划分费用。
注意:若一份只有一个石子,那么,这份石子中最大重量与最小重量的差的平方为0。
输入样例
4 2
4 7 10 1
输出样例
18
解题思路
- 先将石子重量从小到大排序(从大到小也可以).
- 假设f(n, m)表示:n 个石头分成 m 份的最小费用. 特别的,有 f(1,1)=0; f(n,1)=(an - a1)^2
那么,除去最后一份石头的若干个,前面 m - 1 份必定也是最优的分法.
若最后一堆1个石头, f(n,m) = f(n-1,m-1)+0^2
若最后一堆2个石头, f(n,m) = f(n-2,m-1)+(an - an-1)^2
若最后一堆3个石头, f(n,m) = f(n-3,m-1)+(an - an-2)^2
…
最后一堆最多只能有 n - m + 1 个石头,因为当最后一堆为 n - m + 1 时,前面 m - 1 堆已经是一个一份了.
因此, f(n,m) = Min{ f(n-1,m-1)+0^2, f(n-2,m-1)+(an - an-1)^2, …}
例如:
n=5, m=2
a[1…5] = 1 3 4 8 9
f(5,2)=Min{ f(4,1)+0; f(3,1)+1; f(2,1)+5^2 }=Min{49,10,29}=10
这5个石头分2堆的最优分法:(1 3 4)(8 9)
1. dp 方程定义
- dp[n][m]; // 前 n 堆石头分成 m 份
2. 状态转移方程
dp[n][m] = dp[n - k][m - 1] + (a[n] - a[n - k + 1]) * (a[n] - a[n - k + 1])
假设最后一份石头有 k 堆,则 dp[n][m] 为前 n - k 堆中挑选出 m - 1 份的得分,加上最后这一份的石头得分,得分为最后一堆减去第一堆的平方(因为已经升序排序,所以最大的在这一份最后,最小的一定在这一份开头)
3. 算法解题思路
- 将石堆进行排序(我采取的方法是升序排序),保证随意取一段区间,该区间中最小质量石堆一定在开头,最大质量石堆一定在最尾。
- 外层循环为遍历到前 i 堆,第二层循环为划分为 j 份,第三层循环为最后一份中有 k 堆,对于第三层循环,通过一个变量 minNum 来获取到当最后一份有不同石堆数时,最小得分是多少。
- 最后当跳出第三层循环,即该份数下划分完毕时,对 dp[i][j] 进行赋值即可 dp[i][j] = minNum;
更多注释可查看下方的完整代码中,有助于理解
代码如下
#include <iostream>
#include <cmath>
#include <string.h>
#include<algorithm>
/*
4 2
4 7 10 1
5 2
1 3 4 8 9
*/
using namespace std;
int a[1001];
int dp[1001][1001]; // 前 n 堆石头分成 m 份
int main()
{
int i, j, k, n, m;
cin >> n >> m;
for(i = 1; i <= n; i++) {
cin >> a[i];
}
sort(a + 1, a + n + 1);
memset(dp, 1e9, sizeof(dp));
// 前 i 堆石头划分成1堆,只有一种分法,也就是全部放一起
for(i = 1; i <= n; i++) {
dp[i][1] = (a[i] - a[1]) * (a[i] - a[1]);
}
for(i = 2; i <= n; i++) {
for(j = 2; j <= m; j++) {
// 划分后,最后一堆石头有 k 个,例如如果5堆石头划分成2份,最后一堆石头可以有1,2,3,4堆
int minNum = 1e9;
for(k = 1; k <= i - j + 1; k++) {
minNum = min(minNum, dp[i - k][j - 1] + (a[i] - a[i - k + 1]) * (a[i] - a[i - k + 1]));
//cout << "i=" << i << " j=" << j << " k=" << k << " dp[" << i-k << "][" << j - 1 << "]=" << dp[i - k][j - 1] << " a=" << a[i] - a[i - k + 1] << endl;
}
dp[i][j] = minNum;
//cout << "dp[" << i << "][" << j << "]=" << dp[i][j] << endl;
}
}
cout << dp[n][m] << endl;
return 0;
}
最后
对我感兴趣的小伙伴可查看以下链接
- 我的掘金主页:https://juejin.cn/user/1302297507801358
- 博客主页:http://blog.zhangjiancong.top/
- 公众号:Smooth前端成长记录