【洛谷】【区间与环形dp】P1880 [NOI1995] 石子合并

题目描述

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

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

输入格式

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

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

输出格式

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

输入输出样例

输入 #1复制

4
4 5 9 4

输出 #1复制

43
54

说明/提示

1≤N≤100,0≤ai​≤20。

#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;

// 解题思路:
// 区间dp + 环形
// 在末尾补上凑成环形,例如 4594 则变成 4594 459以转成环形
// 可以发现以n内任一作为起点都可以得到环形长度
// 结果则在1~n为起点的长度为n的n条链中,枚举可得

inline void read(int &x)
{
    register int f = 1;
    x = 0;
    register char c = getchar();
    while (c > '9' || c < '0')
    {
        if (c == '-')
            f = -1;
        c = getchar();
    }
    while (c >= '0' && c <= '9')
    {
        x = (x << 3) + (x << 1) + (c ^ 48);
        c = getchar();
    }
    x *= f;
}

const int maxn = 2e2 + 10;
const int inf = 1 << 30;
// 分别保存原数组,前缀和,最小得分,最大得分
int a[maxn], sum[maxn], dp[maxn][maxn], dp2[maxn][maxn];
int main()
{
    int n; read(n);
    
    // 初始化处理 和 前缀和
    memset(dp, 0x3f, sizeof(dp));
    for(int i = 1; i < 2 * n; i++)
    {
        dp2[i][i] = dp[i][i] = 0;
        if(i <= n)
        {
            read(a[i]);
            sum[i] = sum[i - 1] + a[i];
        }
        else sum[i] = sum[i - 1] + a[i - n];
    }
    
    // 区间环形dp 
    // 状态转移方程为:dp[i][j] = min(dp[i][j], dp[i][k] + dp[k][j] + sum[j] - sum[i]);
    // 最外层枚举区间长度
    for(int len = 1; len < n; len++)
    {
        // 环形dp,所以n后面的也一样要同步dp
        // 如果是 i <= n,那么n后的就不会更新了,下次如果长度覆盖到的话,值就不正确
        // 第二层枚举起点
        for(int i = 1; i + len < 2*n; i++)
        {
            // j 为终点
            int j = i + len;
            // 最内层枚举分割点
            for(int k = i; k < j; k++)
            {
                dp[i][j] = min(dp[i][j], dp[i][k] + dp[k + 1][j] + sum[j] - sum[i - 1]);
                dp2[i][j] = max(dp2[i][j], dp2[i][k] + dp2[k + 1][j] + sum[j] - sum[i - 1]);
            }
        }
    }

    // 分别初始化为最大最小值
    int ans1(inf), ans2(0);
    // 从所有链中分别找到最大最小值
    for(int i = 1; i <= n; i++)
    {
        ans1 = min(ans1, dp[i][i + n - 1]);
        ans2 = max(ans2, dp2[i][i + n - 1]);
    }
    printf("%d\n%d\n", ans1, ans2);
    return 0;
}

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值