题目:
有N
堆石子排成一排(n<=100
),现要将石子有次序地合并成一堆,规定每次只能选相邻的两堆合并成一堆,并将新的一堆的石子数,记为改次合并的得分,编一程序,由文件读入堆数n
及每堆石子数(<=200
);
(1)选择一种合并石子的方案,使得做n-1
次合并,得分的总和最少;
(2)选择一种合并石子的方案,使得做n-1
次合并,得分的总和最多;
输入格式
第一行为石子堆数n
第二行为每堆石子数,每两个数之间用一空格分隔。
输出格式
从第1
行为得分最小
第2
行是得分最大。
样例
样例输入
4
4 5 9 4
样例输出
44
54
特点:
仅是一排石子,而且只能是相邻两个石子相邻可以合并,所以第1
个石子只能与右边的合并,第n
个石子只能与左边的合并,所以用区间dp简单枚举合并即可。
数组表示:(以下同)
f[i][j]
:从i
到j
的区间的最大/最小值;
sum[i]
:i
的前缀和(便于计算);
动态规划:
阶段:
区间长度d
,d=1
时初始值即石子本身分数,所以从d=2
开始枚举,以此到d=n
;
状态:
从i
到j
的区间的最大/最小值;
决策:
是否将从i
到k
的区间与从k+1
到j
的区间合并,若合并即加上sum[i]-sum[j-1]
(i
到j
的分数);
动态转移方程:
f[i][j]=min(f[i][j],f[i][k]+f[k+1][j]+sum[i]-sum[j-1]);
f[i][j]=max(f[i][j],f[i][k]+f[k+1][j]+sum[i]-sum[j-1]);
注意:
最小值f[i][j]
的初始值为0x7f7f7f7f
;
最大值f[i][j]
的初始值为0
;
代码:
#include <bits/stdc++.h> using namespace std; const int maxn=100+50; const int INF=0x3f3f3f3f; int n; int f1[maxn][maxn],f2[maxn][maxn]; int sum[maxn]; int a[maxn]; int minx=INF,maxx=0; void MIN(){ for(int d=2;d<=n;d++){ for(int i=1,j;(j=i+d-1)<=n;i++){ for(int k=i;k<j;k++){ f1[i][j]=min(f1[i][j],f1[i][k]+f1[k+1][j]+sum[j]-sum[i-1]); } } } } void MAX(){ for(int d=2;d<=n;d++){ for(int i=1,j;(j=i+d-1)<=n;i++){ for(int k=i;k<j;k++){ f2[i][j]=max(f2[i][j],f2[i][k]+f2[k+1][j]+sum[j]-sum[i-1]); } } } } int main(){ memset(f1,INF,sizeof(f1)); scanf("%d",&n); for(int i=1;i<=n;i++){ scanf("%d",&a[i]); } for(int i=1;i<=n;i++){ if(i==1){ sum[i]=a[i]; }else{ for(int j=1;j<=i;j++){ sum[i]+=a[j]; } } } for(int i=1;i<=n;i++){ f1[i][i]=0; } MIN(); MAX(); printf("%d\n",f1[1][n]); printf("%d\n",f2[1][n]); return 0; }
<2>
题目:
在一个园形操场的四周摆放N
堆石子,现要将石子有次序地合并成一堆.规定每次只能选相邻的2
堆合并成新的一堆,并将新的一堆的石子数,记为该次合并的得分。
试设计出1
个算法,计算出将N
堆石子合并成1
堆的最小得分和最大得分。
输入格式
数据的第1
行试正整数N,(1≤ N ≤100)
,表示有N
堆石子。
第2
行有N
个数,分别表示每堆石子的个数。
输出格式
输出共2
行,第1
行为最小得分,第2
行为最大得分。
样例
样例输入
4
4 4 5 9
样例输出
43
54
特点:
与上一题不同的是,这里是圆形操场,即现在的石子是一个环,现在第1
个石子与第n
个石子算是相邻,也是很简单,自己将数组存储两边石子的分数,便可以模拟环,然后直接按上次的dp处理即可。
注意:
此时的d
还是枚举到n
;
而i
和j
则需要枚举到2*n
;
代码:
#include <bits/stdc++.h> using namespace std; const int maxn=200+50; const int INF=0x3f3f3f3f; int n; int f1[maxn][maxn],f2[maxn][maxn]; int sum[maxn]; int a[maxn]; int minx=INF,maxx=0; void MIN(){ for(int d=2;d<=n;d++){ for(int i=1,j;(j=i+d-1)<=2*n;i++){ f1[i][j]=INF; for(int k=i;k<j;k++){ f1[i][j]=min(f1[i][j],f1[i][k]+f1[k+1][j]+sum[j]-sum[i-1]); } } } } void MAX(){ for(int d=2;d<=n;d++){ for(int i=1,j;(j=i+d-1)<=2*n;i++){ f2[i][j]=0; for(int k=i;k<j;k++){ f2[i][j]=max(f2[i][j],f2[i][k]+f2[k+1][j]+sum[j]-sum[i-1]); } } } } int main(){ scanf("%d",&n); for(int i=1;i<=n;i++){ scanf("%d",&a[i]); a[n+i]=a[i]; } for(int i=1;i<=2*n;i++){ sum[i]=sum[i-1]+a[i]; } MIN(); MAX(); int zuix=INF,zuid=0; for(int i=1;i<=n;i++){ zuix=min(zuix,f1[i][i+n-1]); zuid=max(zuid,f2[i][i+n-1]); } printf("%d\n%d\n",zuix,zuid); return 0; }
<3>
题目:
在一个园形操场的四周摆放N
堆石子,现要将石子有次序地合并成一堆.规定每次只能选相邻的2
堆合并成新的一堆,并将新的一堆的石子数,记为该次合并的得分。
试设计出1
个算法,计算出将N
堆石子合并成1
堆最大得分。
输入格式
数据的第1
行试正整数N,1≤N≤2000
,表示有N
堆石子.第2
行有N
个数,分别表示每堆石子的个数。
输出格式
输出共1
行,最大得分。
样例
样例输入
4
4 4 5 9
样例输出
54
注意:
虽然一看这题与<2>
完全一样,但是仔细看的话,N
的范围发生了变化,那么就不能与<1>、<2>
一样用直接dp的方法,上两次只有N3的效率,所以这次得需要优化。
区间dp的优化方式有四边不等式法,但是这里是求最大值,没有单调性,无法用四边不等式。
但是最大值有一个性质,总是在两个端点的最大者中取到。
动态转移方程式:
f[i][j]=max(f[i][j-1],f[i+1][j])+sum[j]-sum[i-1]
;
代码:
#include<bits/stdc++.h> using namespace std; const int maxn=1e6+5,INF=0x3f3f3f3f; int n,m,f[4003][4003],ans,a[maxn],sum[4000003]; int main(){ cin>>n; for(int i=1;i<=n;i++){ cin>>a[i]; a[i+n]=a[i]; sum[i]=a[i]+sum[i-1]; } for(int i=n+1;i<=2*n;i++){ sum[i]=sum[i-1]+a[i]; } for(int d=2;d<=n;d++){ for(int i=1,j;(j=i+d-1)<=n*2;i++){ f[i][j]=max(f[i][j-1],f[i+1][j])+sum[j]-sum[i-1]; } } int ansmax=0; for(int i=1;i<=n;i++){ ansmax=max(ansmax,f[i][i+n-1]); } cout<<ansmax<<endl; return 0; }