石子合并——区间dp

<1>

题目:

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]:从ij的区间的最大/最小值;

sum[i]i的前缀和(便于计算);

动态规划:

阶段:

区间长度dd=1时初始值即石子本身分数,所以从d=2开始枚举,以此到d=n

状态:

ij的区间的最大/最小值;

决策:

是否将从ik的区间与从k+1j的区间合并,若合并即加上sum[i]-sum[j-1]ij的分数);

动态转移方程:

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

ij则需要枚举到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的方法,上两次只有的效率,所以这次得需要优化。

区间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;
}

 

 

猜你喜欢

转载自www.cnblogs.com/Rubyonly233/p/12807927.html
今日推荐