区间dp就是在一系列的区间中搞特一些子dp,昂。
这玩意儿还得用具体的题目来说。
下面展示一些模型。
题目链接:codevs石子合并
这道题目跟合并果子很像啊!但是这道题目只能合并相邻的两堆石子。怎么合并的总得分最大呢,就要保证每一次合并后,得分都最大。符合最优性原则,可以用我们神奇的DP做。
这道题目就是典型的一个区间DP。我们可以知道,每一个区间的最优解都是由更小的区间的最优解合并而来,所以我们划分阶段的依据就是区间长度。再枚举起点,算出终点,进行计算。
#include
#define maxn 350
#define INF 2000000000
using namespace std;
inline int read()
{
int num=0;
bool flag=true;
char c;
for(;c>'9'||c<'0';c=getchar())
if(c=='-')
flag=false;
for(;c>='0'&&c<='9';num=num*10+c-48,c=getchar());
return flag ? num : -num;
}//快读,背好就行。
int n,a[maxn],f[maxn][maxn],sum[maxn];
/*
f[i][j]表示区间[I,j]的石头最小得分(最优解)。
sum是一个前缀和数组,很好用。a数组是原数据。
*/
int main()
{
n=read();
memset(f,0,sizeof(f)); //初始化
for(int i=1;i<=n;i++)
{
a[i]=read();
sum[i]=sum[i-1]+a[i];//处理前缀和
}
for(int len=2;len<=n;len++)
//枚举长度
for(int i=1;i<=n-len+1;i++)
//枚举起点
{
int j=i+len-1;
//求出终点
int temp=INF;
for(int k=i;k//枚举所有的合并点。
temp=min(temp,f[i][k]+f[k+1][j]+sum[j]-sum[i-1]);//这就是我们著名的区间dp著名状态转移方程。
f[i][j]=temp;//求出最优解。
}
printf("%d\n",f[1][n]);//输出
return 0;
}
题目链接:luogu石子合并
这道题目跟上一题唯一的不一样就是这里的石头是环状排列的,这意味着我们需要进行一点不一样的预处理——断环为链。
方法就是——扩大两倍(看了代码就理解了)。
#include
#define maxn 350
#define INF 2000000000
using namespace std;
inline int read()
{
int num=0;
bool flag=true;
char c;
for(;c>'9'||c<'0';c=getchar())
if(c=='-')
flag=false;
for(;c>='0'&&c<='9';num=num*10+c-48,c=getchar());
return flag ? num : -num;
}//快读
int n,a[maxn*2],sum[maxn*2];
//a,原数组。sum:前缀和数组
int f1[maxn*2][maxn*2],f2[maxn*2][maxn*2];
//f1:放最小值;f2:放最大值
int main()
{
n=read();
for(int i=1;i<=n;i++)
{
a[i]=read();
a[n+i]=a[i];
}//著名的断环为链
for(int i=1;i<=2*n;i++)
sum[i]=sum[i-1]+a[i];
//当然两倍之后,前缀和也要搞两倍
for(int len=2;len<=n;len++)
for(int i=1;i<=2*n-len+1;i++)
//同上一题
{
int j=i+len-1;
int mx=-INF,mi=INF;
for(int k=i;k1][j]+sum[j]-sum[i-1]);
mi=min(mi,f1[i][k]+f1[k+1][j]+sum[j]-sum[i-1]);
}
f1[i][j]=mi;
f2[i][j]=mx;//一样的骚操作
}
int ansmin=INF,ansmax=-INF;
for(int i=1;i<=n;i++)
{
ansmin=min(ansmin,f1[i][i+n-1]);
ansmax=max(ansmax,f2[i][i+n-1]);
}
/*由于是原题是环,意味着我们这里有不一样的操作,
我们需要枚举不同的断开点*/
printf("%d\n%d",ansmin,ansmax);
return 0;
}
题目链接:luogu能量项链
这道题目跟合并果子实在是太像了。只要处理一下计算收益的部分就OK了。
#include
#define maxn 120*2
#define INF 2000000000
using namespace std;
inline int read()
{
int num=0;
bool flag=true;
char c;
for(;c>'9'||c<'0';c=getchar())
if(c=='-')
flag=false;
for(;c>='0'&&c<='9';num=num*10+c-48,c=getchar());
return flag ? num : -num;
}
int f[maxn][maxn],n;
struct node
{
int head,tail;
}a[maxn];
int main()
{
n=read();
for(int i=1;i<=n;i++)
a[i].head=a[i+n].head=read();
for(int i=1;i<=2*n-1;i++)
a[i].tail=a[i+1].head;
a[2*n].tail=a[1].head;
for(int len=2;len<=n;len++)
for(int i=1;i<=2*n-len+1;i++)
{
int j=i+len-1;
int temp=-INF;
for(int k=i;k1][j]+a[i].head*a[k].tail*a[j].tail);
}
f[i][j]=temp;
}
int ans=-INF;
for(int i=1;i<=n;i++)
ans=max(ans,f[i][i+n-1]);
printf("%d",ans);
return 0;
}