AcWing 1068 环形石子合并

题目描述:

将 n 堆石子绕圆形操场排放,现要将石子有序地合并成一堆。

规定每次只能选相邻的两堆合并成新的一堆,并将新的一堆的石子数记做该次合并的得分。

请编写一个程序,读入堆数 n 及每堆的石子数,并进行如下计算:

  • 选择一种合并石子的方案,使得做 n−1 次合并得分总和最大。
  • 选择一种合并石子的方案,使得做 n−1次合并得分总和最小。

输入格式

第一行包含整数 n,表示共有 n 堆石子。

第二行包含 n 个整数,分别表示每堆石子的数量。

输出格式

输出共两行:

第一行为合并得分总和最小值,

第二行为合并得分总和最大值。

数据范围

1≤n≤200

输入样例:

4
4 5 9 4

输出样例:

43
54

分析:

在做本题前需要先熟悉AcWing 282 石子合并问题,本题的重点在于如何处理环形问题。在普通的石子合并问题中,我们可以通过枚举最后一次合并的位置k来求出石子合并的最小代价。状态表示:f[i][j]表示合并区间[i,j]上的石子所花费的最小代价,状态转移方程为f[i][j] = min(f[i][j],f[i][k]+f[k+1][j]+w[i][j]),其中w[i][j]表示合并区间[i][j]中石子质量之和。状态转移的过程可以通过第一重循环枚举区间长度len,第二重枚举区间起点l,第三重枚举最后一次合并的位置k来实现。问题在于如何将环形石子合并问题转化为普通的石子合并问题。

对于一堆环形的石子,我们采用某种顺序去合并,最后必然是将某两堆合并为一堆,而且环形的石子必然有两堆石子间存在缺口。

本题的合并顺序,环形石子最后出现的缺口位置也不同,设环形的序列是a1,a2,...,an,(an与a1也是相邻的)。则缺口可能出现在a1到a2,a2到a3,...,an到a1之间,我们可以用一重循环枚举缺口出现的位置,知道了缺口在哪,就相当于把环形的石子切开成一个线性排列的石子,就可以用普通的石子合并的解法去做了。但是本题n的范围是200,枚举缺口的一重循环加上普通石子合并问题的三重循环,时间复杂度是O(n^4),也就是8亿级别的运算,会超时。考虑优化下本题的解法,根据前面的分析可知,环形石子合并问题相当于求n次普通的石子合并,即分别求a1到an,a2,a3,...,an,a1,等等n个区间的石子合并问题,通常采取的办法就是将普通的石子序列后面再加一个石子序列,形成一个长度为2n的石子序列,即a1,a2,...,an,a1,a2,...,an,从前往后枚举长度为n的区间分别是a1到an,a2到a1,a3到a2等等,也就是环形石子合并问题所需要的区间,都出现在了这样一个长度为2n的区间中,我们对这样一个长度为2n的区间做下石子合并,最后遍历下所有长度为n区间的合并代价的最大值和最小值就是本题所求的答案了。

#include <iostream>
#include <algorithm>
using namespace std;
const int N = 405;
int a[N],s[N],f[N][N],g[N][N];
int main(){
    int n;
    cin>>n;
    for(int i = 1;i <= n;i++){
        cin>>a[i];
        a[i + n] = a[i];
    }
    for(int i = 1;i <= 2*n;i++) s[i] = s[i-1] + a[i];
    for(int len = 2;len <= n;len++){
        for(int l = 1;l + len - 1 <= 2*n;l++){
            int r = l + len - 1;
            f[l][r] = 1e9;
            for(int k = l;k < r;k++){
                f[l][r] = min(f[l][r],f[l][k]+f[k+1][r]+s[r]-s[l-1]);
                g[l][r] = max(g[l][r],g[l][k]+g[k+1][r]+s[r]-s[l-1]);
            }
        }
    }
    int res1 = 1e9,res2 = 0;
    for(int i = 1;i <= n;i++){
        res1 = min(res1,f[i][i+n-1]);
        res2 = max(res2,g[i][i+n-1]);
    }
    cout<<res1<<endl<<res2<<endl;
    return 0;
}

 注意本题既要求最小代价又要求最大代价,所以需要分别用两个dp数组f和g存储,另外注意边界情况的处理,求最小代价时初始情况所有长度为1区间内的石子f的值都是0,而长度大于1区间内石子f的值都是无穷大,而求最大代价的g数组则无需额外处理。(关于石子合并问题的四边形优化就继续往后拖再写啦!

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
环形石子合并问题可以使用动态规划来解决。下面是解决该问题的动态规划思路: 1. 定义状态:设dp[i][j]表示合并第i堆到第j堆石子所能得到的最小得分和最大得分。 2. 状态转移方程:对于dp[i][j],可以考虑最后一次合并的位置k,其中i <= k < j。假设最后一次合并的位置是k,则有以下两种情况: - 合并第i堆到第k堆和合并第k+1堆到第j堆,得到的得分为dp[i][k] + dp[k+1][j] + sum[i][j],其中sum[i][j]表示第i堆到第j堆石子的总数。 - 合并第i堆到第j堆,得到的得分为dp[i][j-1] + sum[i][j]。 综上所述,状态转移方程为: dp[i][j] = min(dp[i][k] + dp[k+1][j] + sum[i][j]),其中i <= k < j dp[i][j] = max(dp[i][j-1] + sum[i][j]) 3. 边界条件:当i == j时,只有一堆石子,得分为0。 4. 计算顺序:根据状态转移方程,可以先计算小区间dp值,再逐步扩大区间,最终计算出dp[n]的值。 下面是一个使用动态规划解决环形石子合并问题的示例代码: ```python def mergeStones(stones): n = len(stones) dp = [[0] * (n+1) for _ in range(n+1)] sum = [0] * (n+1) for i in range(1, n+1): sum[i] = sum[i-1] + stones[i-1] for length in range(2, n+1): for i in range(1, n-length+2): j = i + length - 1 dp[i][j] = float('inf') for k in range(i, j): dp[i][j] = min(dp[i][j], dp[i][k] + dp[k+1][j] + sum[j] - sum[i-1]) return dp[1][n] stones = [4, 1, 2, 3] min_score = mergeStones(stones) max_score = mergeStones(stones) print("最小得分:", min_score) print("最大得分:", max_score) ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值