0.总结
Get to the points first. The article comes from LawsonAbs!
- 区间
dp
是常见的一种dp
题。本文给出一些求解区间dp
常用的套路 - 博客来源:
LawsonAbs@CSDN
1.固定套路
区间dp
的套路是固定的。
- step1.确定区间长度
len
,一般是len
属于[0,n]
。按照从小到大的顺序遍历一次,这个作为dp问题处理的阶段。 - step2.知道区间长度之后,接着确定区间的端点。因为区间长度已经确定好了,当知道了区间的左端点,区间的右端点就可以通过
j=i+len-1
计算出来 - step3.这一步就是针对具体问题进行一个具体的分析,同时做一个状态的转移即可。
- step4.再复杂一点儿的情况,可能给你的这个序列并不是一个线性的,而是一个环,那么就可能需要将这个环断成一个链,这个链是原来序列的2倍长。例如
1 2 3
是一个环,那么我们就生成一个序列1 2 3 1 2 3
。以此来解决存在环的问题。
2.练习题
2.1 【洛谷】P1880 石子合并
这是一道任何书中讲区间dp
都会涉及到的模板题。可AC的代码如下:
#include<iostream>
#include<cstdio>
#include<cmath>
using namespace std;
int n,minl,maxl,f1[300][300],f2[300][300],num[300];
int s[300];
inline int d(int i,int j){return s[j]-s[i-1];}
//转移方程:f[i][j] = max(f[i][k]+f[k+1][j]+d[i][j];
int main()
{
scanf("%d",&n);
for(int i=1;i<=n+n;i++) //因为是一个环,所以需要开到两倍再枚举分界线,最后肯定是最大的
{
scanf("%d",&num[i]);
num[i+n]=num[i];
s[i]=s[i-1]+num[i];
}
for(int p=1;p<n;p++)
{
for(int i=1,j=i+p;(j<n+n) && (i<n+n);i++,j=i+p)
{
f2[i][j]=999999999;
for(int k=i;k<j;k++)
{
f1[i][j] = max(f1[i][j], f1[i][k]+f1[k+1][j]+d(i,j));
f2[i][j] = min(f2[i][j], f2[i][k]+f2[k+1][j]+d(i,j));
}
}
}
minl=999999999;
for(int i=1;i<=n;i++)
{
maxl=max(maxl,f1[i][i+n-1]);
minl=min(minl,f2[i][i+n-1]);
}
printf("%d\n%d",minl,maxl);
return 0;
}
2.2 【洛谷】 P1063 能量项链
下面是我写的代码:
#include<iostream>
using namespace std;
const int N = 205;
//dp[i][j]表示左端点为i,长度为j时的最大值
//arr[i][0]表示为头,arr[i][1]表示为尾
//left[i][j]表示区间[i,j] 取最大值时的左端点值; right[i][j]表示区间[i,j]取最大值是右端点的值
int dp[N][N],lef[N][N],righ[N][N],arr[N][2];
int n;
int main(){
cin >> n;
for(int i = 1;i<=n;i++){
cin >> arr[i][0];
arr[i-1][1] = arr[i][0] ;//上个珠子的尾等于次头
}
arr[n][1] = arr[1][0] ;//重新计算第一颗珠子的尾
//预处理部分
for(int i = 1;i<=n;i++){
// cout << arr[i][0]<<","<< arr[i][1]<<"\n";
lef[i][i] = arr[i][0];
righ[i][i] = arr[i][1];
}
for(int i = 1;i<=n;i++){//断链成环
arr[n+i][0] = arr[i][0];
arr[n+i][1] = arr[i][1];
lef[n+i][n+i] = lef[i][i];
righ[n+i][n+i] = righ[i][i];
}
//开始计算
for(int len = 2;len<=n;len++){//len的范围在[2,n]
for(int i = 1;i<=2*n;i++){//区间的左端点
int y = i+len-1;//区间的右端点
for(int k = i;k<y && y<=2*n; k++){
int temp = lef[i][k] * righ[i][k] * righ[k+1][y] + dp[i][k-i+1] + dp[k+1][y-k];
if(dp[i][len] < temp){
dp[i][len] = temp;
lef[i][y] = lef[i][k];
righ[i][y] = righ[k+1][y];
}
}
}
}
int res = 0;
for(int i = 1;i<=n;i++){
res = max(res,dp[i][n]);
}
cout << res<<"\n";
}
虽然可AC,但是稍显冗余,其原因是,dp
数组设的不好,导致需要用到lef,righ
两个数组,很明显,这个是可以避免的。下面再给出一个精简版的代码:
#include <bits/stdc++.h>
using namespace std;
int f[405][405];
int n,a[205];
int main()
{
cin >> n;
for(int i=1;i<=n;i++) //***对环形问题的处理技巧***
{
cin >> a[i];
a[n+i]=a[i];
}
for(int i=2;i<=n+1;i++)
{
for(int l=1;l+i-1<=2*n;l++) //如果采取了上述策略,一定要将2*n个点都更新
{
int r=l+i-1;
for(int k=l+1;k<=l+i-2;k++)
f[l][r]=max(f[l][r],f[l][k]+f[k][r]+a[l]*a[k]*a[r]);
}
}
int res=0;
for (int i=1;i<=n;i++) res=max(res,f[i][n+i]);
cout << res;
return 0;
}