NOI1995:石子合并

文章介绍了如何使用区间型动态规划算法解决将N堆石子合并成1堆的问题,计算最小和最大得分,涉及状态转移方程和前缀和的计算。
摘要由CSDN通过智能技术生成

题目链接

[NOI1995] 石子合并

题目描述

在一个圆形操场的四周摆放 N N N 堆石子,现要将石子有次序地合并成一堆,规定每次只能选相邻的 2 2 2 堆合并成新的一堆,并将新的一堆的石子数,记为该次合并的得分。

试设计出一个算法,计算出将 N N N 堆石子合并成 1 1 1 堆的最小得分和最大得分。

输入格式

数据的第 1 1 1 行是正整数 N N N,表示有 N N N 堆石子。

2 2 2 行有 N N N 个整数,第 i i i 个整数 a i a_i ai 表示第 i i i 堆石子的个数。

输出格式

输出共 2 2 2 行,第 1 1 1 行为最小得分,第 2 2 2 行为最大得分。

样例 #1

样例输入 #1

4
4 5 9 4

样例输出 #1

43
54

提示

1 ≤ N ≤ 100 1\leq N\leq 100 1N100 0 ≤ a i ≤ 20 0\leq a_i\leq 20 0ai20

算法思想

每次只能选相邻的 两堆合并成新的一堆,那么可以使用区间型动态规划的思想进行处理,下面以最小得分为例。

状态表示

f [ i ] [ j ] f[i][j] f[i][j]表示从第 i i i堆石子一直合并到第 j j j堆石子的最小得分

状态计算

从最小的合并长度 2 2 2开始计算,以每次合并为阶段,枚举合并的起点,根据最后一次合并的位置可以分为下面几种情况:

  • 最后一次在 i i i位置合并,即将第 i i i堆石子和后面的 [ i + 1... j ] [i+1...j] [i+1...j]堆石子合并,得到的分数为 f [ i ] [ i ] + f [ i + 1 ] [ j ] + s [ i . . . j ] f[i][i]+f[i+1][j]+s[i...j] f[i][i]+f[i+1][j]+s[i...j]
  • 最后一次在 i + 1 i+1 i+1位置合并,即将前面的 [ i . . . i + 1 ] [i...i+1] [i...i+1]堆石子和后面的 [ i + 2... j ] [i+2...j] [i+2...j]堆石子合并,得到的分数为 f [ i ] [ i + 1 ] + f [ i + 2 ] [ j ] + s [ i . . . j ] f[i][i+1]+f[i+2][j]+s[i...j] f[i][i+1]+f[i+2][j]+s[i...j]
  • 最后一次在 k k k位置合并,即将前面的 [ i . . . k ] [i...k] [i...k]堆石子和后面的 [ k + 1... j ] [k+1...j] [k+1...j]堆石子合并,得到的分数为 f [ i ] [ i + 1 ] + f [ i + 2 ] [ j ] + s [ i . . . j ] f[i][i+1]+f[i+2][j]+s[i...j] f[i][i+1]+f[i+2][j]+s[i...j]
  • 最后一次在 j − 1 j-1 j1位置合并,即将前面的 [ i . . . j − 1 ] [i...j-1] [i...j1]堆石子和第 j j j堆石子合并,得到的分数为 f [ i ] [ j − 1 ] + f [ j ] [ j ] + s [ i . . . j ] f[i][j-1]+f[j][j]+s[i...j] f[i][j1]+f[j][j]+s[i...j]

f [ i ] [ j ] f[i][j] f[i][j]为以上情况的最小值。其中 s [ i . . . j ] s[i...j] s[i...j]表示本次合并得到的分数,也就是第 i i i堆石子到第 j j j堆石子的分数和,可以使用前缀和计算得到。

初始状态

  • 为计算最小值 f [ i ] [ j ] f[i][j] f[i][j]应初始化为无穷大
  • f [ i ] [ i ] f[i][i] f[i][i]表示合并1堆,无效状态应初始化为0。

除此之外,由于是在一个圆形操场的四周摆放 N N N 堆石子,也就是说可以从任何一点出发进行合并。因此,需要采用拆环为链的方式进行处理,最后求以任意起点开始求合并分数的最小值。

时间复杂度

状态数为 n × n n\times n n×n,状态计算时需要枚举最后一次合并位置,因此时间复杂度为 O ( n 3 ) O(n^3) O(n3)

代码实现

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 110 * 2, INF = 0x3f3f3f3f;
int f[N][N], g[N][N];
int a[N], s[N];

int main()
{
    int n;
    cin >> n;
    for(int i = 1; i <= n; i ++) 
    {
        cin >> a[i];
        a[n + i] = a[i]; //拆环为链
    }
    //计算前缀和
    for(int i = 1; i < 2 * n; i ++) s[i] = s[i - 1] + a[i];
    //枚举每次合并的长度,最小长度为2    
    for(int len = 2; len <= n; len ++)
        for(int i = 1; i + len - 1 < 2 * n; i ++) //枚举开始合并的位置
        {
            int j = i + len - 1; //合并结束的位置
            f[i][j] = INF; //初始状态
            //枚举最后一次合并的位置k
            for(int k = i; k < j; k ++)
            {
                f[i][j] = min(f[i][j], f[i][k] + f[k + 1][j] + s[j] - s[i - 1]); //最小得分
                g[i][j] = max(g[i][j], g[i][k] + g[k + 1][j] + s[j] - s[i - 1]); //最大得分
            }
        }
    
    int minx = 1e9, maxx = 0;
    for(int i = 1; i <= n; i ++) //枚举合并起点
    {
        minx = min(minx, f[i][i + n - 1]);
        maxx = max(maxx, g[i][i + n - 1]);
    }
    cout << minx << '\n' << maxx;
    
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

少儿编程乔老师

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值