把以前写的博客搬运过来……
题目链接:传送门
经典区间dp题,同时要用到断环为链的思想,断环为链在题目讲解里叙述
----------------------------------分割线---------------------------------------
子状态:
d
p
[
i
]
[
j
]
dp[i][j]
dp[i][j] 表示从第i颗到第j颗珠子合并后释放能量的最大值。
分析:
设这个区间的左端点为
j
j
j,右端点为
p
p
p。
设
a
a
a为第
j
j
j颗到第
k
k
k颗珠子合并后释放的最大值,即
d
p
[
j
]
[
k
]
dp[j][k]
dp[j][k],设
b
b
b为
d
p
[
k
+
1
]
[
p
]
dp[k+1][p]
dp[k+1][p]。合并后增加了
a
[
j
]
∗
a
[
k
+
1
]
∗
a
[
p
+
1
]
a[j]*a[k+1]*a[p+1]
a[j]∗a[k+1]∗a[p+1]。所以可以推出状态转移方程:
d
p
[
j
]
[
p
]
=
m
a
x
(
d
p
[
j
]
[
p
]
,
d
p
[
j
]
[
k
]
+
d
p
[
k
+
1
]
[
p
]
+
a
[
j
]
∗
a
[
k
+
1
]
∗
a
[
p
]
dp[j][p]=max(dp[j][p],dp[j][k]+dp[k+1][p]+a[j]*a[k+1]*a[p]
dp[j][p]=max(dp[j][p],dp[j][k]+dp[k+1][p]+a[j]∗a[k+1]∗a[p]
对断环为链的理解:
最后一个珠子的尾标记为第一颗珠子的头标记,构成了一条环。此时若用普通的判断容易出错(而且可能状态转移方程会很复杂)。
若将记录的数组开大两倍,则第
n
+
1
,
n
+
2
,
…
…
,
2
n
n+1,n+2,……,2n
n+1,n+2,……,2n颗珠子分别与第
1
,
2
,
…
…
,
n
1,2,……,n
1,2,……,n颗珠子是等价的。
于是
d
p
[
2
]
[
n
+
1
]
dp[2][n+1]
dp[2][n+1]就表示第
2
2
2个珠子到第
n
+
1
n+1
n+1个珠子合并后释放能量的最大值,即第一颗珠子和最后一颗珠子合并。
易错点:
第一层循环:应该枚举区间长度。
第二层循环:应该枚举区间的左端点的位置,并由左端点和长度算出右端点的位置。
第三层循环:应该枚举断点(将左边合并成的珠子与右边合并成的珠子合并)。
---------------------------------分割线--------------------------------------
代码:
#include<stdio.h>
#include<cstring>
using namespace std;
const int Size=605;
int a[Size];
int dp[Size][Size]; //dp[i][j] 从第i颗珠子到第j颗释放能量的最大值
int main() {
int n,len,ans=0;
scanf("%d",&n);
len=n<<1; //两倍长度
for(int i=1; i<=n; i++)
scanf("%d",&a[i]);
for(int i=n+1; i<=len; i++) //复制数组
a[i]=a[i-n];
int f,p;
for(int i=2; i<len; i++) { //枚举区间长度
f=len-i+1;
for(int j=1; j<f; j++) { //枚举左端点
p=i+j-1; //算出右端点
for(int k=j; k<p; k++) {
int tmp=dp[j][k]+dp[k+1][p]+a[j]*a[k+1]*a[p+1];
//合并释放的能量
if(tmp>dp[j][p]) dp[j][p]=tmp; //更新答案
}
}
}
//找出把前多少个珠子合并到最后的珠子是最优解
for(int i=1; i<=n; i++)
if(dp[i][i+n-1]>ans)
ans=dp[i][i+n-1]; //更新答案
printf("%d",ans);
return 0;
}
/*
dp[j][p]=max(dp[j][p],dp[j][k]+dp[k+1][p]+a[j]*a[k+1]*a[p];
*/