洛谷 P1880 [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

4
4 5 9 4

输出 #1

43
54

说明/提示

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

题目解析:

让我们先看看样例: 这是最小值的求法,看起来好像和贪心很像啊!!!但其实只是样例数据给出的假象罢了.
很多的大佬和蒟蒻做题时用了贪心结果只有30分!
首先如何解决上图环的问题呢?  
当然很简单啦,我们把它存成一条链:即把 T T T存成 2 ∗ T 2*T 2T
如:2 3 4 6 5 4 2 3 4 6 5 4 这样每次枚举 i i i i + N − 1 i+N-1 i+N1就可以了是吧是不是很简单啊( i ≤ N i\leq N iN)
上文我们说到这是一题动规,那么我们来分析一下:

  1. 根据题意可知每次都是两堆石子合并成一堆,并且这两堆石子是相邻的!!
    那么这两堆石子又是由另外的石子合并的,那么我们可以认为i到j堆石子是由 F [ i ] [ k ] F[i][k] F[i][k] F [ k + 1 ] [ j ] F[k+1][j] F[k+1][j]合成的。那么 F [ i ] [ k ] F[i][k] F[i][k]也是根据上面的规则求得到!

  2. 那么合成的分数如何表示的呢?
    已知每个点的分数都是确定的,那么无论前面的数据如何合并的分数一定是由前面 s u m [ j ] − s u m [ i − 1 ] sum[j]-sum[i-1] sum[j]sum[i1]的值, s u m [ i ] = s u m [ i − 1 ] + T [ i ] sum[i]=sum[i-1]+T[i] sum[i]=sum[i1]+T[i]; 因此得到 F [ i ] [ j ] = m a x ( F [ i ] [ k ] + [ k + 1 ] [ j ] + s u m [ j ] − s u m [ i − 1 ] , F [ i ] [ j ] ) F[i][j]=max(F[i][k]+[k+1][j]+sum[j]-sum[i-1],F[i][j]) F[i][j]=max(F[i][k]+[k+1][j]+sum[j]sum[i1],F[i][j]); 至此这题大水题已经解决了剩下的只是要考虑合并几次的问题而已

因为第一次至少两堆合并,那么就有了 L ( L = 2 ; L < = N ; + + L ) j = i + L − 1 L (L=2;L<=N;++L)j=i+L-1 L(L=2L<=N;++L)j=i+L1;
最后就是求一下答案枚举一遍就行了

代码

/*************************************************
Note:
*************************************************/
#include <queue>
#include <stack>
#include <set>
#include <stdio.h>
#include <iostream>
#include <vector>
#include <iomanip>
#include <string.h>
#include <algorithm>
#include <cmath>
#include <cstring>
#define ll long long
#define ull unsigned long long
using namespace std;
const int N = 1e5 + 10;
const int INF = 0x3f3f3f3f;
const int MAXN = INF, MINN = -INF;
inline int read()
{
    int s = 0, w = 1;
    char ch = getchar();
    while (ch < '0' || ch > '9')
    {
        if (ch == '-')
            w = -1;
        ch = getchar();
    }
    while (ch >= '0' && ch <= '9')
        s = s * 10 + ch - '0', ch = getchar();
    return s * w;
}
int T[210], F1[210][210], F2[210][210], sum[210];
int main(void)
{
    int N = read();
    for (int i = 1; i <= N; ++i)
        T[i] = read(), T[i + N] = T[i];
    for (int i = 1; i <= 2 * N; ++i)
        sum[i] = sum[i - 1] + T[i], F1[i][i] = 0, F2[i][i] = 0;
    for (int L = 2; L <= N; ++L)
    {
        for (int i = 1; i <= 2 * N - L + 1; ++i)
        {
            int j = i + L - 1;
            F1[i][j] = MAXN, F2[i][j] = MINN;
            for (int k = i; k < j; ++k)
            {

                F1[i][j] = min(F1[i][k] + F1[k + 1][j], F1[i][j]);
                F2[i][j] = max(F2[i][k] + F2[k + 1][j], F2[i][j]);
            }
            F1[i][j] += (sum[j] - sum[i - 1]);
            F2[i][j] += (sum[j] - sum[i - 1]);
        }
    }
    int ANS1 = MAXN, ANS2 = MINN;
    for (int i = 1; i <= N; ++i)
    {
        ANS1 = min(ANS1, F1[i][i + N - 1]);
        ANS2 = max(ANS2, F2[i][i + N - 1]);
    }
    printf("%d\n%d", ANS1, ANS2); 
    return 0;
}
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

꧁Q༒ོγ꧂

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

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

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

打赏作者

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

抵扣说明:

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

余额充值