目录
一、线形区间dp
1.1 石子合并(模板题)
题目介绍
题目链接:P1775 石子合并(弱化版) - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)https://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;
}