算法设计与分析 SCAU8597 石子划分问题

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


解题思路

  1. 先将石子重量从小到大排序(从大到小也可以).
  2. 假设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. 算法解题思路
  1. 将石堆进行排序(我采取的方法是升序排序),保证随意取一段区间,该区间中最小质量石堆一定在开头,最大质量石堆一定在最尾。
  2. 外层循环为遍历到前 i 堆,第二层循环为划分为 j 份,第三层循环为最后一份中有 k 堆,对于第三层循环,通过一个变量 minNum 来获取到当最后一份有不同石堆数时,最小得分是多少。
  3. 最后当跳出第三层循环,即该份数下划分完毕时,对 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;
}


最后

对我感兴趣的小伙伴可查看以下链接

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值