题310.区间dp-acwing-Q1068–环形石子合并
一、题目
二、题解
用dp五步法分析该题(以求最大得分为例):
1.确定dp数组,并明确其含义。dp[l][r]表示[l,r]区间石子合并所能得到的最大得分
2.确定递推公式,对于链状的石子合并问题用y式dp分析法。
则递推公式如下:
dp[l][r]=max(dp[l][r],dp[l][k]+dp[k+1][r]+(sum[r]-sum[l]))
3.初始化dp数组。当区间长度为1时,无需合并显然得分为0,即对于dp值初始化为0,由于求max,所以其余初始化为-Inf
4.确定遍历顺序。这是一个环形石子合并问题,为了处理这个环形,我们可以采用将环拉成链的形式,即把输入链延长两倍,变成 2n 个石堆,其中 i 和 i+n 是相同的两个堆,然后直接套之前确定的递推公式求解。而此时需用len从前向后遍历区间长度([1,n]),l从前向后遍历左端点,并确定r(<2n),k从前向后遍历分界点(分法,[1,r))
5.打印dp数组
代码如下:
#include <bits/stdc++.h>
using namespace std;
const int maxn=410;
const int Inf=0x3f3f3f3f;
int n;
int a[maxn],sum[maxn];
int dp_min[maxn][maxn],dp_max[maxn][maxn];//[l][r]区间,合并得分的最小值,最大值
int main()
{
cin>>n;
for(int i=1;i<=n;i++)//将环形拉成一条链,方法就是再补一条原链在原链的尾巴后面
{
cin>>a[i];
a[i+n]=a[i];
}
for(int i=1;i<=2*n;i++)//预处理得到前缀和,方便后序区间合并求对应区间的石子总数
{
sum[i]=sum[i-1]+a[i];
}
fill(dp_min[0],dp_min[0]+maxn*maxn,Inf);
fill(dp_max[0],dp_max[0]+maxn*maxn,-Inf);
for(int len=1;len<=n;len++)//枚举区间长度
{
for(int l=1;l+len-1<=2*n-1;l++)//枚举左端点,由于是原链+原链,所以l+len-1对应右端点的位置最大可达2*n-1(如果到2n,则会l=n+1,r=2n,此时和1~n石子取重了)
{
int r=l+len-1;//得到右端点
if(len==1)//当区间长度为1时,不用合并,所以得分自然为0
{
dp_min[l][r]=dp_max[l][r]=0;
}
else//枚举区间分割点
{
for(int k=l;k<r;k++)
{
//k位置分割对应的dp值为k之前的dp值(由于先求的小区间,已保证该值为对应最小/大,k之后同理)+k之后的dp值+整个lr区间的石子总数
dp_min[l][r]=min(dp_min[l][r],(dp_min[l][k]+dp_min[k+1][r]+sum[r]-sum[l-1]));
dp_max[l][r]=max(dp_max[l][r],(dp_max[l][k]+dp_max[k+1][r]+sum[r]-sum[l-1]));
}
}
}
}
int res_min=Inf,res_max=-Inf;
for(int i=1;i<=n;i++)//枚举原链+原链中选择不同的n长度区间,看哪个为结果值
{
res_min=min(res_min,dp_min[i][i+n-1]);
res_max=max(res_max,dp_max[i][i+n-1]);
}
cout<<res_min<<endl;
cout<<res_max<<endl;
}