一、简单情况

(1)问题描述

有N堆石子,现要将石子有序的合并成一堆,规定如下:每次只能移动任意的2堆石子合并,合并花费为新合成的一堆石子的数量。求将这N堆石子合并成一堆的总花费最小(或最大)。

例题:

Description

佳佳的爷爷有一个苹果园,最近苹果都熟了,都从树上掉了下来,佳佳要帮爷爷把树下的苹果堆到一起,他每次可以任意选两堆合成一堆,耗费的体力是这两堆苹果苹果数量的和。他最后的目标是将所有的果子堆成一个大堆,那么他最少耗费的体力是多少呢?

Input

多组测试数据,每组数据输入的第一行是一个数n,表示果子原来有多少堆,接下来n个数,每个数表示每堆苹果的个数。

Output

对于每组数据,输出一个整数,表明佳佳最少需要的体力。

Sample Input

3
      1 1 1

Sample Output

5

(2)分析

由于每次可以选择任意的两堆,所以可以选择贪心算法,一种简单的思路是使用sort方法排序之后每次取数组的前两位相加,合并后的新堆需要按大小放到后面剩余的位置上即可。代码如下:

#include <iostream>
#include <algorithm>
#define N 300
using namespace std;
const int INF=1<<10;
int main(){int n,i,j,k;while(cin>>n){int resmin=0;int a[n];for(i=0;i<n;i++){cin>>a[i];}sort(a,a+n);for(i=0;i+1<n;i++){resmin+=a[i]+a[i+1];a[i+1]=a[i]+a[i+1];k=i+1;for(j=i+2;j<n;j++){if(a[k]>a[j]){int temp=a[j];a[j]=a[k];a[k]=temp;k=j;}}}cout<<resmin<<endl;}return 0;}

但是在OJ上会TimeLimitExceeded,算法本身结果没问题,但是超时了。那么可以换一种思路,该数组的性质符合stl的queue中的优先队列,直接用优先队列构造这组数据即可更高效的实现,代码如下:

#include <iostream>
#include <queue>
using namespace std;
int main()
{int n;while (cin>>n){priority_queue<int,vector<int>,greater<int> > que;   //greater< >指定是小顶堆 ,即从小到大排列 int i, crt, cur, a, m, resmin = 0;for (i = 0; i < n; ++i){scanf("%d", &a);que.push(a);}while (que.size() > 1){crt = que.top();que.pop();cur = que.top();que.pop();m =cur + crt;resmin += cur + crt;que.push(m);}cout<<resmin<<endl;}system("pause");return 0;
}

以上代码可以AC。到此最简单的情况基本解决了,下面讨论复杂一些的情况。

二、线型情况

(1)问题描述

在一个操场上摆放着一行共n堆的石子。现要将石子有序地合并成一堆。规定每次只能选相邻的两堆合并成新的一堆,并将新的一堆石子数记为该次合并的得分。请编辑计算出将n堆石子合并成一堆的最小得分和将n堆石子合并成一堆的最大得分。

Input

输入第一行为n(n<1000),表示有n堆石子,第二行为n个用空格隔开的整数,依次表示这n堆石子的石子数量(<=1000)

Output

输出将n堆石子合并成一堆的最小得分和将n堆石子合并成一堆的最大得分。

 Sample Input

3

1 2 3

 Sample Output

9 11

(2)分析

每次只能选择相邻的两堆合并,所以继续使用贪心算法会有问题,原理是局部最优与全局最优并不是完全一致的。考虑使用动态规划的方法。动态规划需要建立状态转移方程,首先考虑最后一步时的状态,即将两大堆合为一堆,那么只要保证这两堆已为最优解即可。此时有两种思路:

思路一:建立二维素组dp[ ][ ],dp[i][j]代表从第i个石堆合并到第j个石堆的最优解,0<=i<=j<n;建立数组sum[ ],sum[i]代表从第0个石堆加到第i个石堆的总和,因为要合并一个区间的石堆,无论顺序如何最终一次合并的得分都为所有石堆的总和;

状态转移方程:以求最小得分为例:

即将i~j堆石头的最优解分解为i~k段加上k+1~j段最优解的和加上最后将这两段合并的代价temp:

c++实现代码:

#include <iostream>
using namespace std;
const int INF = 1 << 30;
#define N  1000int dpmin[N][N],dpmax[N][N];
int sum[N];
int a[N],resmin,resmax;int main()
{int n;while(cin>>n){for(int i=0;i<n;i++)cin>>a[i];sum[0] = a[0];for(int i=1;i<n;i++)sum[i] = sum[i-1] + a[i];for(int i=0;i<n;i++)dpmin[i][i] =dpmax[i][i] =0; //根据从i堆到i堆相当于不合并,所以为0 for(int v=1;v<n;v++) //步长从1到n-1{for(int i=0;i<n-v;i++)  //从第0堆石头开始循环,i<n-v因为是线型的不能i+步长v不能超出n-1    {int j = i + v;     //求i~j的最优解 dpmin[i][j] = INF;//置i!=j的位置为一较大值 dpmax[i][j]=0;    //置i!=j的位置为一较小值0 int tmp = sum[j] - (i > 0 ? sum[i-1]:0);for(int k=i;k<j;k++){dpmin[i][j] = min(dpmin[i][j],dpmin[i][k]+dpmin[k+1][j] + tmp);dpmax[i][j] = max(dpmax[i][j],dpmax[i][k]+dpmax[k+1][j] + tmp);}}}resmin=dpmin[0][n-1];resmax=dpmax[0][n-1];cout<<resmin<<endl<<resmax<<endl;}return 0;
}

思路二:建立二维素组dp[ ][ ],dp[i][j]代表从第i个石堆开始合并j个石堆(总共j+1个石堆)的最优解,0<=i<n-j,1<=j<n;建立数组sum[ ]含义与上面相同;

状态转移方程:以求最小得分为例,

即将从i堆开始的j堆石头的合并分解为从i堆开始的k堆的最优解加上从i+k+1堆开始的j-k-1堆石头的最优解再加上将这两段合并的代价temp:

c++实现代码:

#include <iostream>
using namespace std;
const int INF=1<<10;
#define N 200
int a[N],sum[N];
int dpmax[N][N],dpmin[N][N];
int resmin,resmax;
int main(){int n,i,j,k,t;while(cin>>n){for(i=0;i<n;i++){cin>>a[i];}sum[0]=a[0];for(i=1;i<n;i++){sum[i]=sum[i-1]+a[i];}for(i=0;i<n;i++){  //置从i开始的0推石头为0 dpmax[i][0]=0;dpmin[i][0]=0;}for(j=1;j<n;j++){ //从某一位置开始的1堆循环到某一位置开始的n-1堆 for(i=0;i<n-j;i++){ //从第0堆石头开始循环,i<n-v因为是线型的不能i+步长v不能超出n-1    dpmin[i][j] = INF;//置j!=0的位置为一较大值 dpmax[i][j] = 0;  //置j!=0的位置为一较小值0 for(k=0;k<j;k++){int temp=sum[i+j]-(i>0? sum[i-1]:0);dpmin[i][j]=min(dpmin[i][j],dpmin[i][k]+dpmin[i+k+1][j-k-1]+temp);dpmax[i][j]=max(dpmax[i][j],dpmax[i][k]+dpmax[i+k+1][j-k-1]+temp);}}}resmin=dpmin[0][n-1];resmax=dpmax[0][n-1];cout<<resmin<<endl<<resmax<<endl;}return 0;}

三、环型情况

(1)问题描述

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

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

    Input

数据的第1行试正整数N,1≤N≤100,表示有N堆石子.第2行有N个数,分别表示每堆石子的个数.

    Output

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

Sample Input

4
4 4 5 9

Sample Output

43
     54

(2)分析

此种情况与第二种情况大体相同,区别是石子是环形摆放的,那么在状态方程迭代时要进行一些对n取模处理。

思路一:  思路与情况二大体一致,不再赘述。相当于对n排石子求了最优解,第一排0~n-1堆,第二排1~n-1加上0~1,第三排2~n-1加上0~2,···

c++代码:

#include <iostream>
#include <string.h>
#include <stdio.h>using namespace std;
const int INF = 1 << 30;
const int N = 200;int dpmin[N][N];
int dpmax[N][N];
int sum[N],a[N];
int resmin,resmax;
int n,i,j,k,v;int getsum_ring(int i,int j)  //因为是环形使求temp变得复杂,需要对区间i和j进行判断
{if(j < i) return getsum_ring(i,n-1) + getsum_ring(0,j);//如果j<i,那么相当于j超出了n-1到了环的小端,相当于(sum[n-1]-sump[i-1])+(sum[j]-0) else return sum[j] - (i>0 ? sum[i-1]:0);
}void dp(int a[],int n)
{for(i=0;i<n;i++)dpmin[i][i] = dpmax[i][i] = 0;//根据从i堆到i堆相当于不合并,所以为0 for(v=1;v<n;v++) //步长从1到n-1{for(i=0;i<n;i++){j=(i+v)%n;  //因为是一个环,所以i+v可以超过n-1,进行对n取模操作,n等价于0,n+1等价于1 dpmin[i][j] = INF;//置i!=j的位置为一较大值 dpmax[i][j] = 0;  //置i!=j的位置为一较小值0 for(k=0;k<v;k++){dpmin[i][j] = min(dpmin[i][j],dpmin[i][(i+k)%n] + dpmin[(i+k+1)%n][j] + getsum_ring(i,j)); //i+k,i+k+1都有可能超过1,所以都对n取模 dpmax[i][j] = max(dpmax[i][j],dpmax[i][(i+k)%n] + dpmax[(i+k+1)%n][j] + getsum_ring(i,j));}}}resmin = dpmin[0][n-1];resmax = dpmax[0][n-1];for(i=0;i<n;i++)   //相当于对n排石子求了最优解,第一排0~n-1堆,第二排1~n-1加上0~1,第三排2~n-1加上0~2,···现在在这n种情况中再找出最优解 {resmin = min(resmin,dpmin[i][(i+n-1)%n]);resmax = max(resmax,dpmax[i][(i+n-1)%n]);}
}int main()
{while(cin>>n){for(i=0;i<n;i++)cin>>a[i];sum[0] = a[0];for(i=1;i<n;i++)sum[i] = sum[i-1] + a[i];dp(a,n);cout<<resmin<<endl;cout<<resmax<<endl;}return 0;
}

思路二: 思路与情况二大体一致,不再赘述。一些细节问题需要重新处理,例如temp的计算

c++代码:

#include <iostream>
using namespace std;
const int INF = 1 << 30;
const int N = 200;int dpmin[N][N];
int dpmax[N][N];
int sum[N],a[N];
int resmin,resmax;
int n;int getsum_ring(int i,int j) //因为是环形使求temp变得复杂,需要对区间i和j进行判断
{if(i+j >= n) return getsum_ring(i,n-i-1) + getsum_ring(0,(i+j)%n);//如果i+j>=n ,那么此时temp应为i堆之后的n-i-1堆(等价于sum[n-1]-sum[i-1])之和加上0~(i+j)%n堆之和 else return sum[i+j] - (i>0 ? sum[i-1]:0);
}void dp(int a[],int n)
{for(int i=0;i<n;i++)    //置从i开始的0推石头为0 dpmin[i][0] = dpmax[i][0] = 0;for(int j=1;j<n;j++)  //从某一位置开始的1堆循环到某一位置开始的n-1堆 {for(int i=0;i<n;i++) //从第0堆石头开始循环,i<n因为是环形的可以超出n-1    {dpmin[i][j] = INF;//置j!=0的位置为一较大值 dpmax[i][j] = 0;  //置j!=0的位置为一较小值0 for(int k=0;k<j;k++){dpmin[i][j] = min(dpmin[i][j],dpmin[i][k] + dpmin[(i+k+1)%n][j-k-1] + getsum_ring(i,j)); // dpmin[i][k]中的k代表从i堆开始的k堆,不代表堆得序号所以不用对N取模,dpmax[i][j] = max(dpmax[i][j],dpmax[i][k] + dpmax[(i+k+1)%n][j-k-1] + getsum_ring(i,j));//dpmin[(i+k+1)%n][j-k-1]中的(i+k+1)代表堆序号且可能超过n-1所以需要取模 }}}resmin = dpmin[0][n-1];resmax = dpmax[0][n-1];for(int i=0;i<n;i++)  //相当于对n排石子求了最优解,第一排0~n-1堆,第二排1~n-1加上0~1,第三排2~n-1加上0~2,···现在在这n种情况中再找出最优解 {resmin = min(resmin,dpmin[i][n-1]);resmax = max(resmax,dpmax[i][n-1]);}
}int main()
{while(cin>>n){for(int i=0;i<n;i++)cin>>a[i];sum[0] = a[0];for(int i=1;i<n;i++)sum[i] = sum[i-1] + a[i];dp(a,n);cout<<resmin<<endl;cout<<resmax<<endl;}return 0;
}

合并石子问题研究与实现相关推荐

  1. 蒟蒻吃药计划-治疗系列 #round 2 合并石子+乘积最大

    1.合并石子 <信息学奥赛一本通>第五版 P371 第三节 T1 我就直接开始讲吧: Warning:这个题目和 合并果子 不一样!不一样!不一样!不一样!不一样!不一样!不一样!不一样! ...

  2. 合并石子 四边形不等式优化

    题目描述 有一排石子,共n 堆.现要将石子有次序地合并成一堆.规定每次只能选相邻的2 堆石子合并成新的一堆,并将新的一堆石子数记为该次合并的得分.试设计一个算法,计算出将n堆石子合并成一堆的最小得分. ...

  3. 蓝桥杯-合并石子(java)

    算法提高 合并石子 时间限制:2.0s 内存限制:256.0MB问题描述在一条直线上有n堆石子,每堆有一定的数量,每次可以将两堆相邻的石子合并,合并后放在两堆的中间位置,合并的费用为两堆石子的总数.求 ...

  4. 合并石子 区间dp水题

    合并石子 链接: nyoj 737 描述: 有N堆石子排成一排,每堆石子有一定的数量.现要将N堆石子并成为一堆.合并的过程只能每次将相邻的两堆石子堆成一堆,每次合并花费的代价为这两堆石子的和,经过N- ...

  5. 合并石子(三种方法)

    合并石子 题目 在一个操场上一排地摆放着N堆石子.现要将石子有次序地合并成一堆.规定每次只能选相邻的2堆石子合并成新的一堆,并将新的一堆石子数记为该次合并的得分.请设计一个程序,计算出将N堆石子合并成 ...

  6. Luogu 1880 合并石子

    Luogu 1880 合并石子 (线性动态规划) 经典的区间型动态规划. ---------------------------------- 题干:https://www.luogu.org/pro ...

  7. 有n堆石子,每次取出两堆合成一堆,每堆石子的个数即为合并石子所需要耗费的体力,求出合并所有石子堆所需要耗费的最小体力

    有n堆石子,每次取出两堆合成一堆,每堆石子的个数即为合并石子所需要耗费的体力,求出合并所有石子堆所需要耗费的最小体力 典型的贪心题,即每次取出数量最少的两堆石子合并. 举个例子来说,假如有5堆石子,石 ...

  8. 洛谷 P1775 合并石子(弱化版)

    文章目录 合并石子(弱化版) 一.题目描述 二.思路 三.代码 合并石子(弱化版) 一.题目描述 https://www.luogu.com.cn/problem/P1775 设有 N(N \le 3 ...

  9. 2021.06.03合并石子+能量项链

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

最新文章

  1. 都是套路!要毁灭人类、喷马斯克、还获得公民身份的机器人Sophia,真相确是...
  2. 理解ASP.NET MVC Framework Action Filters(翻的)
  3. 【转】numpy-array自带的迭代器-----np.nditer
  4. 关于 ABAP 调试器里的 16 进制值的显示问题
  5. 公证服务信息_使用多个公证员提高网络吞吐量
  6. Java ClassLoader findLoadedClass()方法与示例
  7. vue学习之二ECMAScript6标准
  8. 《言简意赅之Linux设备驱动编程》 前言
  9. bzoj 4031 [HEOI2015]小Z的房间
  10. 如何通过OCJP认证考试
  11. Office 检测到一个与此文件相关的问题为帮助保护您的计算机,无法打开此文件。
  12. Adobe Audition CC 2019 下载安装教程
  13. GIS应用技巧之缓冲区分析
  14. sqlmap 注入教程 常用命令大全
  15. Kali Linux系统安装教程(虚拟机)
  16. 服务器虚拟机如何连接显示器,Windows 10:如何在VMware虚拟机上使用双显示器
  17. 【博学谷学习记录】超强总结,用心分享丨大数据超神之路(一):Scala基础语法副本
  18. python 跳出两层循环
  19. 锐捷三层交换机route-map设置
  20. 扁平化你的Qt应用程序

热门文章

  1. PowerBI参数字段筛选器
  2. Python 基础学习 locals()用法
  3. C++ OutputDebugString(可变参数)
  4. 设计稿---------
  5. 周杰伦 牛仔很忙 10月10日首播
  6. ajax应用设计模式,Ajax设计模式下Web开发的研究与应用
  7. Ldapsearch
  8. 某小说论坛网爬虫演示
  9. Emacs之为c/c++函数生成调用图(八十)
  10. 开拓新城 · 长沙 | 时速云签约马栏山视研院视频渲染超算云平台项目,携手推进我国视频产业跨越式创新发展