zoukankan      html  css  js  c++  java
  • 石子合并——区间dp

    石子合并(3种变形)

    <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的方法,上两次只有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;
    }

     

     

  • 相关阅读:
    软件工程 团队开发(2)
    软件工程 团队开发(1)
    大道至简阅读笔记01
    小工具集合用户模板和用户场景
    人月神话阅读笔记03
    人月神话阅读笔记02
    本周java学习
    本周学习总结
    本周java 学习进度报告
    《构建之法》读后感
  • 原文地址:https://www.cnblogs.com/Rubyonly233/p/12807927.html
Copyright © 2011-2022 走看看