合并石子问题研究与实现
一、简单情况
(1)问题描述
有N堆石子,现要将石子有序的合并成一堆,规定如下:每次只能移动任意的2堆石子合并,合并花费为新合成的一堆石子的数量。求将这N堆石子合并成一堆的总花费最小(或最大)。
例题:
Description
Input
Output
Sample Input
1 1 1
Sample Output
(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。到此最简单的情况基本解决了,下面讨论复杂一些的情况。
二、线型情况
在一个操场上摆放着一行共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;}
三、环型情况
在一个园形操场的四周摆放N堆石子,现要将石子有次序地合并成一堆.规定每次只能选相邻的2堆合并成新的一堆,并将新的一堆的石子数,记为该次合并的得分。
试设计出1个算法,计算出将N堆石子合并成1堆的最小得分和最大得分.
数据的第1行试正整数N,1≤N≤100,表示有N堆石子.第2行有N个数,分别表示每堆石子的个数.
输出共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;
}
合并石子问题研究与实现相关推荐
- 蒟蒻吃药计划-治疗系列 #round 2 合并石子+乘积最大
1.合并石子 <信息学奥赛一本通>第五版 P371 第三节 T1 我就直接开始讲吧: Warning:这个题目和 合并果子 不一样!不一样!不一样!不一样!不一样!不一样!不一样!不一样! ...
- 合并石子 四边形不等式优化
题目描述 有一排石子,共n 堆.现要将石子有次序地合并成一堆.规定每次只能选相邻的2 堆石子合并成新的一堆,并将新的一堆石子数记为该次合并的得分.试设计一个算法,计算出将n堆石子合并成一堆的最小得分. ...
- 蓝桥杯-合并石子(java)
算法提高 合并石子 时间限制:2.0s 内存限制:256.0MB问题描述在一条直线上有n堆石子,每堆有一定的数量,每次可以将两堆相邻的石子合并,合并后放在两堆的中间位置,合并的费用为两堆石子的总数.求 ...
- 合并石子 区间dp水题
合并石子 链接: nyoj 737 描述: 有N堆石子排成一排,每堆石子有一定的数量.现要将N堆石子并成为一堆.合并的过程只能每次将相邻的两堆石子堆成一堆,每次合并花费的代价为这两堆石子的和,经过N- ...
- 合并石子(三种方法)
合并石子 题目 在一个操场上一排地摆放着N堆石子.现要将石子有次序地合并成一堆.规定每次只能选相邻的2堆石子合并成新的一堆,并将新的一堆石子数记为该次合并的得分.请设计一个程序,计算出将N堆石子合并成 ...
- Luogu 1880 合并石子
Luogu 1880 合并石子 (线性动态规划) 经典的区间型动态规划. ---------------------------------- 题干:https://www.luogu.org/pro ...
- 有n堆石子,每次取出两堆合成一堆,每堆石子的个数即为合并石子所需要耗费的体力,求出合并所有石子堆所需要耗费的最小体力
有n堆石子,每次取出两堆合成一堆,每堆石子的个数即为合并石子所需要耗费的体力,求出合并所有石子堆所需要耗费的最小体力 典型的贪心题,即每次取出数量最少的两堆石子合并. 举个例子来说,假如有5堆石子,石 ...
- 洛谷 P1775 合并石子(弱化版)
文章目录 合并石子(弱化版) 一.题目描述 二.思路 三.代码 合并石子(弱化版) 一.题目描述 https://www.luogu.com.cn/problem/P1775 设有 N(N \le 3 ...
- 2021.06.03合并石子+能量项链
2021.06.03合并石子+能量项链 题目描述 在一个圆形操场的四周摆放 N 堆石子,现要将石子有次序地合并成一堆,规定每次只能选相邻的2堆合并成新的一堆,并将新的一堆的石子数,记为该次合并的得分. ...
最新文章
- 都是套路!要毁灭人类、喷马斯克、还获得公民身份的机器人Sophia,真相确是...
- 理解ASP.NET MVC Framework Action Filters(翻的)
- 【转】numpy-array自带的迭代器-----np.nditer
- 关于 ABAP 调试器里的 16 进制值的显示问题
- 公证服务信息_使用多个公证员提高网络吞吐量
- Java ClassLoader findLoadedClass()方法与示例
- vue学习之二ECMAScript6标准
- 《言简意赅之Linux设备驱动编程》 前言
- bzoj 4031 [HEOI2015]小Z的房间
- 如何通过OCJP认证考试
- Office 检测到一个与此文件相关的问题为帮助保护您的计算机,无法打开此文件。
- Adobe Audition CC 2019 下载安装教程
- GIS应用技巧之缓冲区分析
- sqlmap 注入教程 常用命令大全
- Kali Linux系统安装教程(虚拟机)
- 服务器虚拟机如何连接显示器,Windows 10:如何在VMware虚拟机上使用双显示器
- 【博学谷学习记录】超强总结,用心分享丨大数据超神之路(一):Scala基础语法副本
- python 跳出两层循环
- 锐捷三层交换机route-map设置
- 扁平化你的Qt应用程序