经过在机房里数日的切磋,LYD 从杜神牛那里学会了分离与合体,出关前,杜神牛给了他一个测试……
杜神牛造了 n n n个区域,他们紧邻着排成一行,编号 1 … n 1…n 1…n。在每个区域里都放着一把 OI 界的金钥匙,每一把都有一定的价值,LYD 当然想得到他们了。然而杜神牛规定 LYD 不能一下子把他们全部拿走,而是每次只可以拿一把。为了尽快得到所有金钥匙,LYD 自然就用上了刚学的分离与合体特技。
一开始 LYD 可以选择 1 … n − 1 1…n−1 1…n−1 中的任何一个区域进入,我们不妨把这个区域记为 kk。进入后 LYD 会在 k k k区域发生分离,从而分离成两个小 LYD。分离完成的同时会有一面墙在 k k k区域和 k + 1 k+1 k+1区域间升起,从而把 1 … k 1…k 1…k和 k + 1 … n k+1…n k+1…n阻断成两个独立的区间,并在各自区间内任选除区间末尾之外(即从 1 … k − 1 1…k−1 1…k−1 和 k + 1 … n − 1 k+1…n−1 k+1…n−1中选取)的任意一个区域再次发生分离,这样就有了四个小小 LYD……重复以上所叙述的分离,直到每个小 LYD 发现自己所在的区间只剩下了一个区域,那么他们就可以抱起自己梦寐以求的 OI 金钥匙。
但是 LYD 不能就分成这么多个个体存在于世界上,这些小 LYD 还会再合体,合体的小 LYD 所在区间中间的墙会消失。合体会获得 ((合并后所在区间左右端区域里金钥匙价值之和)×(之前分离的时候所在区域的金钥匙价值))。
例如,LYD 曾在 1 … 3 1…3 1…3 区间中的 22 号区域分离成为 1 … 2 1…2 1…2 和 3 … 3 3…3 3…3 两个区间,合并时获得的价值就是 (( 1 1 1号金钥匙价值 + 3 +3 +3号金钥匙价值)×( 2 2 2 号金钥匙价值))。
LYD 请你编程求出最终可以获得的最大总价值,并按照分离阶段从前到后,区域从左到右的顺序,输出发生分离区域编号。若有多种方案,选择分离区域尽量靠左的方案(也可以理解为输出字典序最小的)。
例如先打印一分为二的区域,然后从左到右打印二分为四的分离区域,然后是四分为八的……
第一行一个正整数 n n n.
第二行 n n n个用空格分开的正整数 a i a_i ai ,表示 1 … n 1…n 1…n区域里每把金钥匙的价值。
第一行一个数,表示获得的最大价值
第二行按照分离阶段从前到后,区域从左到右的顺序,输出发生分离区域编号。若有多种方案,选择分离区域尽量靠左的方案(也可以理解为输出字典序最小的)。
7
1 2 3 4 5 6 7
238
1 2 3 4 5 6
数据范围与提示:
对于 20 % 20\% 20% 的数据, n ≤ 10 n≤10 n≤10;
对于 40 % 40\% 40% 的数据, n ≤ 50 n≤50 n≤50;
对于 100 % 100\% 100% 的数据, n , a i ≤ 300 n,a_i≤300 n,ai≤300,保证运算过程和结果不超过 32 32 32位正整数范围。
先来手推一下这个极水样例:
接下来开始合并:
可以发现合并和分裂是互逆的(废话),那么这意味着当我们确定唯一的由单个区间合并的方法后,此时分裂的方法也是唯一确定的,也就是说我们只需求出最优的合并方法,它所对应的分裂方法也就一定是最优的
废话说完了现在让我们来考虑如何合并是最优的,这不就是区间DP的裸题吗?那么我们设dp[i][j]
表示合并 [ i , j ] [i,j] [i,j]可以获得的最大价值,考虑转移:
在 [ i , j ] [i,j] [i,j]中枚举 k k k,表示[i,j]这个区间要由 [ i , k ] [i,k] [i,k]和 [ k + 1 , j ] [k+1,j] [k+1,j]合并而来,如果这样合并,产生的价值也就是(a[i]+a[j])*a[k]
,再加上 [ i , k ] [i,k] [i,k]和 [ k + 1 , j ] [k+1,j] [k+1,j]原有的价值,就是dp[i][k]+dp[k+1][j]+(a[i]+a[j])*a[k]
,与 d p [ i ] [ j ] dp[i][j] dp[i][j]比较,取 m a x max max即可
其实这里大家都很容易想到,主要是输出的问题
想想线性dp中我们是怎样保存路径的,不就是用一个数组存储dp[i]是由那个推出来的吗?到时输出的时候就可以知道每一个最优解是怎么推的,也就可以递归的求出路径了。那线性dp是由之前的推出,所以存储推出它的是什么。区间dp是由小区间推出,不就应该存储小区间的信息吗,在这道题中,大区间由两个小区间推出,决定这两个小区间的就是那个分裂点 k k k. S O SO SO,令 m [ i ] [ j ] m[i][j] m[i][j]表示区间 [ i , j ] [i,j] [i,j]由小区间 [ i , k ] [i,k] [i,k]和 [ k + 1 , j ] [k+1,j] [k+1,j]合并而来为最优,这样就可以知道 [ 1 , n ] [1,n] [1,n]怎么分最优,也就是第一次分裂,同理的可以知道 [ 1 , k ] [1,k] [1,k]和 [ k + 1 , n ] [k+1,n] [k+1,n]怎么分最优,这就是第二次分裂…
注意题目的要求是
先打印一分为二的区域,然后从左到右打印二分为四的分离区域,然后是四分为八的……
也就是说我们要左一个,右一个的输出(不知道你们能不能理解我的意思),不妨把每次分裂的节点看作树的节点,最后就可以形成一个二叉树(我也想知道是什么二叉树),我们应对它逐层遍历,看图理解吧
如上图,表示一个n=7的序列,上图表达的分裂顺序为:
(1, 2, 3, 4, 5, 6, 7)
(1, 2, 3)(4, 5, 6, 7)
(1)(2, 3)(4, 5)(6, 7)
(1)(2)(3)(4)(5)(6)(7)
对这棵树逐层遍历即可(迭代加深和BFS均可)
AC代码:
#include
#include
using namespace std;
const int MAXN=305;
int a[MAXN];
int dp[MAXN][MAXN];
int bb[MAXN][MAXN];
int vis[MAXN][MAXN];
void gg(int i,int j,int dep){
if(i==j) return;
if(dep==0) return;
if (!vis[i][j]) {
printf("%d ",bb[i][j]);
vis[i][j]=1;
}
gg(i,bb[i][j],dep-1);
gg(bb[i][j]+1,j,dep-1);
}
int main(){
int n;
scanf("%d",&n);
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
dp[i][i]=0;
}
for(int len=2;len<=n;len++){
for(int i=1;i<=n-len+1;i++){
int j=i+len-1;
for(int k=i;k<j;k++){
if(dp[i][k]+dp[k+1][j]+(a[i]+a[j])*a[k]>dp[i][j]){
dp[i][j]=dp[i][k]+dp[k+1][j]+(a[i]+a[j])*a[k];
bb[i][j]=k;
}
}
}
}
printf("%d\n",dp[1][n]);
for (int i=1;i<=n;i++){
gg(1,n,i);
}
return 0;
}