赞
踩
(1)问题描述
有N堆石子,现要将石子有序的合并成一堆,规定如下:每次只能移动任意的2堆石子合并,合并花费为新合成的一堆石子的数量。求将这N堆石子合并成一堆的总花费最小(或最大)。
例题:
Description
Input
Output
Sample Input
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 1000
-
- int 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;
- }
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;
- }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。