区间dp问题

目录

一、线形区间dp

1.1 石子合并(模板题)

题目介绍

思路

代码

二、环形区间dp

2.1 环形石子合并

题目

思路

代码


一、线形区间dp

1.1 石子合并(模板题)

题目介绍

题目链接:P1775 石子合并(弱化版) - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)icon-default.png?t=N7T8https://www.luogu.com.cn/problem/P1775


思路

非常经典的区间dp问题,我们只能合并相邻的2个石子堆,在经过一些合并后,就会成为一个个区间,每次合并,就是把2个区间进行合并。

在第一层循环,我们可以将一个每一个大区间分成若干个小区间,划分的标准就是区间的长度len,我们就可以得到每种区间长度下所有的划分情况。

第二层循环就是枚举区间的左端点l,这样就可以确定在每种区间长度下,所有长为len的区间。

第三层循环,就是拆分计算,对于每个确定的区间[l,r],我们枚举在这个区间中,最后合成为1堆的位置k

k的左边合成一堆就是f[l][k],合并的费用就是s[k]-s[l-1]

k的右边合成一堆是f[k+1][r],合并的费用就是s[r]-s[k]

总费用就是

f[l][k]+f[k+1][r]+s[r]-s[k]+s[k]-s[l-1]
即:
f[l][k]+f[k+1][r]+s[r]-s[l-1]

具体分析如下:

代码

#include<iostream>
#include<cstring>

using namespace std;

const int N=330;
const int INF=0x3f3f3f3f;

int n,s[N];
int f[N][N];

int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++)//求前缀和
    {
        scanf("%d",&s[i]);
        s[i]+=s[i-1];
    }
    
    for(int len=2;len<=n;len++)//枚举区间长度
    {
        for(int l=1;l+len-1<=n;l++)//枚举左端点l
        {
            int r=l+len-1;//r代表区间的右端点
            f[l][r]=INF;  //刚开始让区间和为最大值
            for(int k=l;k<r;k++)//枚举分界位置(也可以理解为左区间长度)
                f[l][r]=min(f[l][r],f[l][k]+f[k+1][r]+s[r]-s[l-1]);//比较更新最小值
        }
    } 
    printf("%d",f[1][n]);
    return 0;
}

二、环形区间dp

2.1 环形石子合并

题目

题目链接:https://www.acwing.com/problem/content/1070/

思路

与线性区间dp不同之处就是,这道题是环形,但是我们可以将破环为链,变成线形。

我们可以把链延长两倍,变成 2n个堆,其中i和 i+n是相同的两个堆,然后直接套 线形区间DP 模板,时间复杂度为 O(n^3)

代码

#include<iostream>
#include<cstring>
#include<algorithm>

using namespace std;

const int N=410;
const int INF=0x3f3f3f3f;

int n,w[N],s[N];
int f[N][N],g[N][N];//f最大值,g最小值

int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++)//读入数据
    {
        scanf("%d",&w[i]);
        w[i+n]=w[i];
    }
    for(int i=1;i<=2*n;i++)//前缀和
        s[i]=w[i]+s[i-1];
        
    memset(f,-0x3f,sizeof f);//初始化
    memset(g,0x3f,sizeof g);
    
    for(int len=1;len<=n;len++)//枚举区间长度
    {
        for(int l=1;l+len-1<=2*n;l++)//枚举左端点
        {
            int r=l+len-1;
            if(len==1)//当石子长度为1时,不需要合并
                f[l][r]=g[l][r]=0;
            else
            {
                for(int k=l;k<r;k++)//枚举分界点
                {
                    f[l][r]=max(f[l][r],f[l][k]+f[k+1][r]+s[r]-s[l-1]);//最大值
                    g[l][r]=min(g[l][r],g[l][k]+g[k+1][r]+s[r]-s[l-1]);//最小值
                }
            }
        }
    }
    int Max=-INF,Min=INF;
    for(int l=1;l<=n;l++)//枚举区间长度为n的方案寻找最值
    {
        Max=max(Max,f[l][l+n-1]);
        Min=min(Min,g[l][l+n-1]);
    }
    printf("%d\n%d",Min,Max);
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值