D - 石子合并问题

##D - 石子合并问题
Description
在一个圆形操场的四周摆放着n堆石子。现要将石子有次序地合并成一堆。规定每次只能选相邻的2 堆石子合并成新的一堆,并将新的一堆石子数记为该次合并的得分。试设计一个算法,计算出将n堆石子合并成一堆的最小得分和最大得分。
对于给定n堆石子,计算合并成一堆的最小得分和最大得分。
Input
输入数据的第1行是正整数n,1≤n≤100,表示有n堆石子。第二行有n个数,分别表示每堆石子的个数。
Output
输出数据有两行,第1行中的数是最小得分,第2行中的数是最大得分。
Sample
Input
4
4 4 5 9
Output
43
54

记录一手:
我们怎么才能找到合并的最大值或最小值。
以题中所为例
4
5 4
9
在这个圆形操场有多种方法比如4,4,9,5或4,5,9,4或5,9,4,4……
动态规划的思想最后肯定是先合并了3(n-)堆,最后合并另一堆;
代码:

#include <iostream>

using namespace std;
int n;
int dp[111][111];//d[i][j]表示从第i堆到第j堆得到最小或最大的分数
int p[111][111];//p[i][j]表示从第i堆到第j堆共有多少【石子】
int a[111];//a[i][j]表示第i堆有多少石子
void Init(int *a)
{
    for(int i=1;i<=n;i++)
    {
        p[i][i]=a[i];//从第i堆合并到第i堆共有a[i]个石子
        for(int j=i+1;j<=n;j++)//分别求得第i堆到第i+1堆第i+2堆……石子数量//
        {
            p[i][j]=p[i][j-1]+a[j];
        }
    }
}
int getMax()
{
    for(int r=2;r<=n;r++)//r控制合并长度,r=2,i<=n-(r-1),j=i+(r-1),最大达到n则dp[i][j]最大表示到d[i][n]//
    {
        for(int i=1;i<=n-r+1;i++)
        {
            int j=i+r-1;//j-i=r-1,r每次增加1,比如r=2,d[i][j]可取d[1][2],d[2][3],r增大1d[i][j]可取d[1][3]d[2][4]//
            dp[i][j]=dp[i+1][j]+p[i+1][j]+p[i][i];
            for(int k=i+1;k<j;k++)
            {
                dp[i][j]=max(dp[i][j],dp[i][k]+p[i][k]+dp[k+1][j]+p[k+1][j]);//以k为分界,先求k前面的再求k后面的分数,比如求dp[1][4]就分别求(1堆和2到4堆)和(1到2,2到4)和(1到3堆,第4堆)。再以(1到2,2到4)为例d[1][4]=d[1][2]+d[3][4]+p[1][2]+p[3][4],先是(1,2)(3,4)分别合并得分,第二部合起来的两堆再合并得分为这两堆的石子总数之和//
            }
        }
    }
    return dp[1][n];
}
int getMin()
{
    for(int r=2;r<=n;r++)
    {
        for(int i=1;i<=n-r+1;i++)
        {
            int j=i+r-1;
            dp[i][j]=dp[i+1][j]+p[i+1][j]+p[i][i];
            for(int k=i+1;k<j;k++)
            {
                dp[i][j]=min(dp[i][j],dp[k+1][j]+dp[i][k]+p[k+1][j]+p[i][k]);
            }
        }
    }
    return dp[1][n];
}
int main()
{
    cin>>n;
    int cnt=n;
    int minn=99999;
    int maxx=0;
    for(int i=1;i<=n;i++)cin>>a[i];
    while(cnt--)
    {
        Init(a);
        int tempmax=getMax();
        if(tempmax>maxx)maxx=tempmax;
        Init(a);
        int tempmin=getMin();
        if(tempmin<minn)minn=tempmin;
        int t=a[1];
        for(int i=1;i<n;i++)a[i]=a[i+1];
        a[n]=t;
    }
    cout<<minn<<endl<<maxx<<endl;
    return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
石子合并问题一个经典的动态规划问题,可以用C语言来解决。具体实现方法如下: 1. 定义一个数组dp,表示合并第i个到第j个石子所需的最小代价。 2. 初始化dp数组,将所有dp[i][i]设为0,因为合并一个石子不需要代价。 3. 遍历石子合并的区间长度len,从2开始到n,n为石子的数量。在每个长度下再遍历区间起点i,从1开始到n-len+1。 4. 对于每个区间起点i,计算其终点j=i+len-1。然后遍历所有可能的分割点k,从i开始到j-1。将区间[i, k]和区间[k+1, j]合并,并计算合并的代价。 5. 选取合并代价最小的分割点k,并更新dp[i][j]的值。即dp[i][j]=dp[i][k]+dp[k+1][j]+sum[j]-sum[i-1],其中sum[i]表示石子的前缀和,用于快速计算区间和。 6. 遍历完所有区间长度和起点后,dp[1][n]即为合并所有石子的最小代价。同时可以记录每个dp[i][j]的最小值和最大值,用于输出。 下面是C语言的实现代码: ``` #include <stdio.h> #include <stdlib.h> #include <limits.h> #define MAXN 1005 #define INF INT_MAX int n; int a[MAXN], sum[MAXN]; int dp[MAXN][MAXN], min_dp[MAXN][MAXN], max_dp[MAXN][MAXN]; int min(int a, int b) { return a < b ? a : b; } int max(int a, int b) { return a > b ? a : b; } int main() { scanf("%d", &n); for (int i = 1; i <= n; i++) { scanf("%d", &a[i]); sum[i] = sum[i-1] + a[i]; } for (int i = 1; i <= n; i++) { dp[i][i] = 0; min_dp[i][i] = a[i]; max_dp[i][i] = a[i]; } for (int len = 2; len <= n; len++) { for (int i = 1; i <= n-len+1; i++) { int j = i + len - 1; min_dp[i][j] = INF; max_dp[i][j] = 0; for (int k = i; k < j; k++) { dp[i][j] = dp[i][k] + dp[k+1][j] + sum[j] - sum[i-1]; min_dp[i][j] = min(min_dp[i][j], dp[i][j]); max_dp[i][j] = max(max_dp[i][j], dp[i][j]); } } } printf("%d %d\n", min_dp[1][n], max_dp[1][n]); return 0; } ``` 输入格式:第一行是石子的数量n,接下来一行是n个整数,表示每个石子的价值。 输出格式:一行,包括合并所有石子的最小代价和最大代价。 样例输入: ``` 5 1 2 3 4 5 ``` 样例输出: ``` 33 55 ``` 时间复杂度:O(n^3)。空间复杂度:O(n^2)。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值