0
点赞
收藏
分享

微信扫一扫

算法提高课学习——1.动态规划——1.5区间dp

驚鴻飛雪 2022-03-30 阅读 72

区间dp

1.石子合并

题目描述

设有 N 堆石子排成一排,其编号为 1,2,3,…,N。

每堆石子有一定的质量,可以用一个整数来描述,现在要将这 N 堆石子合并成为一堆。

每次只能合并相邻的两堆,合并的代价为这两堆石子的质量之和,合并后与这两堆石子相邻的石子将和新堆相邻,合并时由于选择的顺序不同,合并的总代价也不相同。

例如有 4 堆石子分别为 1 3 5 2, 我们可以先合并 1、2 堆,代价为 4,得到 4 5 2, 又合并 1,2 堆,代价为 9,得到 9 2 ,再合并得到 11,总代价为 4+9+11=24;

如果第二步是先合并 2,3 堆,则代价为 7,得到 4 7,最后一次合并代价为 11,总代价为 4+7+11=22。

问题是:找出一种合理的方法,使总的代价最小,输出最小代价。

输入格式
第一行一个数 N 表示石子的堆数 N。

第二行 N 个数,表示每堆石子的质量(均不超过 1000)。

输出格式
输出一个整数,表示最小代价。

数据范围
1≤N≤300
输入样例:

输出样例:

解题思路

代码实现

#include<iostream>
#include<cstring>
using namespace std;
const int N=310;
int s[N],f[N][N];
int main()
{
    int n;
    cin>>n;
    memset(f,0x3f,sizeof(f));//记得初始化
    for(int i=1;i<=n;i++){
         cin>>s[i];
         f[i][i]=0;  //初始化
    }
    for(int i=1;i<=n;i++) s[i]+=s[i-1];

    for(int len=2;len<=n;len++){//区间长度一共n堆石子,当n取一的时候就是一堆,不用合并
        for(int l=1;l+len-1<=n;l++){//l~k,k~r-1//区间左端点
            int r=l+len-1;//区间右端点
            for(int k=l;k<r;k++){//k在区间左右端点之间
                f[l][r]=min(f[l][r],f[l][k]+f[k+1][r]+s[r]-s[l-1]);
            }    
        }
    }
    cout<<f[1][n]<<endl;
    return 0;
}

2.环形石子合并

题目描述

将 n 堆石子绕圆形操场排放,现要将石子有序地合并成一堆。

规定每次只能选相邻的两堆合并成新的一堆,并将新的一堆的石子数记做该次合并的得分。

请编写一个程序,读入堆数 n 及每堆的石子数,并进行如下计算:

选择一种合并石子的方案,使得做 n−1 次合并得分总和最大。
选择一种合并石子的方案,使得做 n−1 次合并得分总和最小。
输入格式
第一行包含整数 n,表示共有 n 堆石子。

第二行包含 n 个整数,分别表示每堆石子的数量。

输出格式
输出共两行:

第一行为合并得分总和最小值,

第二行为合并得分总和最大值。

数据范围
1≤n≤200
输入样例:

输出样例:

解题思路

代码实现

#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int N=410;
int f[N][N],s[N],w[N],g[N][N];//f用来求最小,g用来求最大
int main()
{
    int n;
    cin>>n;
    for(int i=1;i<=n;i++){
        cin>>w[i];
        w[i+n]=w[i];
    }
    memset(f,0x3f,sizeof f);
    memset(g,-0x3f,sizeof g);
    for(int i=1;i<=n*2;i++) s[i]=s[i-1]+w[i];
    for(int len=1;len<=n;len++){//枚举长度是n
        for(int i=1;i+len-1<=n*2;i++){//枚举区间是1~2n
            int j=i+len-1;
            if(len==1) f[i][j]=g[i][j]=0;
            else{
                for(int k=i;k<j;k++){
                    f[i][j]=min(f[i][j],f[i][k]+f[k+1][j]+s[j]-s[i-1]);
                    g[i][j]=max(g[i][j],g[i][k]+g[k+1][j]+s[j]-s[i-1]);
                }
            }
        }
    }
    int minf=0x3f3f3f3f;
    int maxf=-0x3f3f3f3f;
    for(int i=1;i<=n;i++){
        minf=min(f[i][i+n-1],minf);
        maxf=max(maxf,g[i][i+n-1]);
    }
    cout<<minf<<endl<<maxf<<endl;
    return 0;
}

3.能量项链

题目描述

在 Mars 星球上,每个 Mars 人都随身佩带着一串能量项链,在项链上有 N 颗能量珠。

能量珠是一颗有头标记与尾标记的珠子,这些标记对应着某个正整数。

并且,对于相邻的两颗珠子,前一颗珠子的尾标记一定等于后一颗珠子的头标记。

因为只有这样,通过吸盘(吸盘是 Mars 人吸收能量的一种器官)的作用,这两颗珠子才能聚合成一颗珠子,同时释放出可以被吸盘吸收的能量。

如果前一颗能量珠的头标记为 m,尾标记为 r,后一颗能量珠的头标记为 r,尾标记为 n,则聚合后释放的能量为 m×r×n(Mars 单位),新产生的珠子的头标记为 m,尾标记为 n。

需要时,Mars 人就用吸盘夹住相邻的两颗珠子,通过聚合得到能量,直到项链上只剩下一颗珠子为止。

显然,不同的聚合顺序得到的总能量是不同的,请你设计一个聚合顺序,使一串项链释放出的总能量最大。

例如:设 N=4,4 颗珠子的头标记与尾标记依次为 (2,3)(3,5)(5,10)(10,2)。

我们用记号 ⊕ 表示两颗珠子的聚合操作,(j⊕k) 表示第 j,k 两颗珠子聚合后所释放的能量。则

第 4、1 两颗珠子聚合后释放的能量为:(4⊕1)=10×2×3=60。

这一串项链可以得到最优值的一个聚合顺序所释放的总能量为 ((4⊕1)⊕2)⊕3)=10×2×3+10×3×5+10×5×10=710。

输入格式
输入的第一行是一个正整数 N,表示项链上珠子的个数。

第二行是 N 个用空格隔开的正整数,所有的数均不超过 1000,第 i 个数为第 i 颗珠子的头标记,当 i<N 时,第 i 颗珠子的尾标记应该等于第 i+1 颗珠子的头标记,第 N 颗珠子的尾标记应该等于第 1 颗珠子的头标记。

至于珠子的顺序,你可以这样确定:将项链放到桌面上,不要出现交叉,随意指定第一颗珠子,然后按顺时针方向确定其他珠子的顺序。

输出格式
输出只有一行,是一个正整数 E,为一个最优聚合顺序所释放的总能量。

数据范围
4≤N≤100,
1≤E≤2.1×109
输入样例:

输出样例:

解题思路

代码实现

#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int N=220;
int f[N][N],w[N];
int main()
{
    int n;
    cin>>n;
    for(int i=1;i<=n;i++){
        cin>>w[i];
        w[i+n]=w[i];
    }
    for(int len=3;len<=n+1;len++){//len从3开始
        for(int i=1;i+len-1<=2*n;i++){
            int j=i+len-1;
            for(int k=i+1;k<j;k++){//k的取值[i+1,j-1]
                f[i][j]=max(f[i][j],f[i][k]+f[k][j]+w[i]*w[k]*w[j]);
            }
        }
    }
    int res=0;
    for(int i=1;i<=n;i++) res=max(res,f[i][i+n]);
    cout<<res<<endl;
    return 0;
}

4.凸多边形的划分

题目描述

给定一个具有 N 个顶点的凸多边形,将顶点从 1 至 N 标号,每个顶点的权值都是一个正整数。

将这个凸多边形划分成 N−2 个互不相交的三角形,对于每个三角形,其三个顶点的权值相乘都可得到一个权值乘积,试求所有三角形的顶点权值乘积之和至少为多少。

输入格式
第一行包含整数 N,表示顶点数量。

第二行包含 N 个整数,依次为顶点 1 至顶点 N 的权值。

输出格式
输出仅一行,为所有三角形的顶点权值乘积之和的最小值。

数据范围
N≤50,
数据保证所有顶点的权值都小于109
输入样例:

输出样例:

解题思路

代码实现

#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
typedef long long LL;
const int N=55,M=35,INF=1e9;
int w[N];
LL f[N][N][M];//M用来表示一个向量  将每一位都存下来

void add(LL a[],LL b[]){//高精度加
    static LL c[M];
    memset(c,0,sizeof c);
    for(int i=0,t=0;i<M;i++){
        t+=a[i]+b[i];
        c[i]=t%10;
        t/=10;
    }
    memcpy(a,c,sizeof(c));
}
void mull(LL a[],int b){//高精度乘
    static LL c[M];
    memset(c,0,sizeof c);
    LL t=0;
    for(int i=0;i<M;i++){
        t+=a[i]*b;
        c[i]=t%10;
        t/=10;
    }
    memcpy(a,c,sizeof(c));
}
int cmp(LL a[],LL b[]){//比较两个高精度数的大小
    for(int i=M-1;i>=0;i--){//注意,由于数是倒叙存放的 所以从后往前比较
        if(a[i]>b[i]) return 1;
        else if(a[i]<b[i]) return -1;
    }
    return 0;
}
void print(LL a[]){
    int k=M-1;
    while(k&&!a[k]) k--;
    while(k>=0) cout<<a[k--];
    cout<<endl;
}
int main()
{
    int n;
    cin>>n;
    LL tmp[M];
    for(int i=1;i<=n;i++) cin>>w[i];
    for(int len=3;len<=n;len++){
        for(int i=1;i+len-1<=n;i++){
            int j=i+len-1;
            f[i][j][M-1]=1;//初始化第M-1位是1,就相当于正无穷,因为数是倒叙放在数组里的
            for(int k=i+1;k<j;k++)
            {
                memset(tmp,0,sizeof tmp);
                tmp[0]=w[i];
                mull(tmp,w[k]);
                mull(tmp,w[j]);
                add(tmp,f[i][k]);
                add(tmp,f[k][j]);
                if(cmp(f[i][j],tmp)>0){
                    memcpy(f[i][j],tmp,sizeof tmp);
                }
            }
        }
    }
   print(f[1][n]);
    return 0;
}

5.加分二叉树

题目描述

解题思路

代码实现

#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int N=35;
int w[N];
int f[N][N],g[N][N];
void dfs(int l,int r){
    if(l>r) return;
    int k= g[l][r];
    cout<<k<<' ';
    dfs(l,k-1);
    dfs(k+1,r);
}
int main()
{
    int n;
    cin>>n;
    for(int i=1;i<=n;i++) cin>>w[i];
    
    for(int len=1;len<=n;len++){
        for(int i=1;i+len-1<=n;i++){
            int j=i+len-1;
            for(int k=i;k<=j;k++){
                int left=k==i?1:f[i][k-1];//如果左子树为空 加分为1
                int right=k==j?1:f[k+1][j];//如果右子树为空 加分为1
                int score=left*right+w[k];
                if(i==j) score=w[k];//如果是叶子结点 就是它本身的分数
                if(score>f[i][j]){
                    f[i][j]=score;
                    g[i][j]=k;//记录当前的根结点
                }
            }
        }
    }
    cout<<f[1][n]<<endl;
    dfs(1,n);
    cout<<endl;
    return 0;
}

初学区间dp,有错误的话,欢迎大家指出,对于题解有什么好的意见也欢迎提出,加油呀~

举报

相关推荐

0 条评论