1278:【例9.22】复制书稿(book)

【算法分析】

动态规划:线性动规

       该题可以抽象为:将由m个数字构成的序列分成k个子段。对于每种子段分割方案,都能求出所有子段中的最大子段和。求所有方案中最大子段和最小的分割子段的方案。

1. 状态定义
集合:子段分割方案
限制:看前几个数字,分成几个子段
属性:最大子段和
条件:最小
统计量:最大子段和
状态定义:dp[i][j]:将前i个数字分成j个子段的方案中最大子段和最小的方案的最大子段和。
初始状态:dp[i][1]:前i个数字分成1段,就是前i个数字的和。

2. 状态转移方程
记数组a的前缀和数组为s,a[i]~a[j]的子段和为s[j]-s[i-1]
集合:将前i个数字分成j个子段的方案
分割集合:根据第j子段的元素个数来分割集合

如果第j子段只有a[i],那么将前i个数字分成j个子段的最大子段和最小的方案的最大子段和,为将前i-1个数字分成j-1个子段的最大子段和最小的方案的最大子段和,再与第j子段比较得到的最大子段和。dp[i][j] = max(dp[i-1][j-1], a[i])
如果第j子段有a[i-1]a[i],那么将前i个数字分成j个子段的最大子段和最小的方案的最大子段和,为将前i-2个数字分成j-1个子段的最大子段和最小的方案的最大子段和,再与第j子段比较得到的最大子段和。dp[i][j] = max(dp[i-2][j-1], a[i]+a[i-1])

如果第j子段为a[j]~a[i]。那么将前i个数字分成j个子段的最大子段和最小的方案的最大子段和,为将前j-1个数字分成j-1个子段的最大子段和最小的方案的最大子段和,再与第j子段的子段和s[i]-s[j-1]比较得到的最大子段和。dp[i][j] = max(dp[j-1][j-1], s[i]-s[j-1])
综上,h从j开始增加到i(h最小时前h-1个数字与子段数j-1相等,有h = j ),取第j子段为a[h]~a[i],有dp[i][j] = max(dp[h-1][j-1], s[i]-s[h-1]))。
以上所有情况取最小值。
 

【参考代码】

#include <bits/stdc++.h>
using namespace std;
#define N 505
int m, k, a[N], s[N];//a:数字序列 s:前缀和
int dp[N][N];//dp[i][j]:将前i个数字分成j个子段的方案中最大子段和最小的方案的最大子段和。
void show(int i, int x)//输出数组a[1]~a[i]中子段和小于等于x的所有子段左右端点(子段长度从小到大) 
{
    if(i == 0)
        return;
    int j, sum = 0;
    for(j = i; j >= 1 && sum+a[j] <= x; --j)
        sum += a[j];
    show(j, x);//输出a[1]~a[j]中的子段 
    cout << j+1 << ' ' << i << endl;  
}
int main()
{
    cin >> m >> k;
    for(int i = 1; i <= m; ++i)
    {
        cin >> a[i];
        s[i] = s[i-1] + a[i];
    }
    memset(dp, 0x3f, sizeof(dp));//dp初始化为无穷大 
    for(int i = 1; i <= m; ++i)//前i个数字分成1段,就是前i个数字的和
        dp[i][1] = s[i];
    for(int i = 1; i <= m; ++i)
        for(int j = 2; j <= k; ++j)
            for(int h = j; h <= i; ++h)//取子段a[h]~a[i] 
                dp[i][j] = min(dp[i][j], max(dp[h-1][j-1], s[i]-s[h-1]));
    show(m, dp[m][k]); 
    return 0;
}

  • 10
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值