小烈送菜
小烈送菜 (dp \(\star\star\))
-
小烈一下碰碰车就被乐满地的工作人员抓住了。作为扰乱秩序的惩罚,小烈必须去乐满地里的“漓江村”饭店端盘子。
-
服务员的工作很繁忙。他们要上菜,同时要使顾客们尽量高兴。一位服务生为 \(n\) 个顾客上菜。这 \(n\) 个顾客坐成一排,小烈从一端的厨房中端出 \(n\) 盘菜(不要问我为什么小烈能一下子端住 \(2500\) 盘菜,他就是能)为 \(n\) 个顾客各上一道相同的菜。
-
显然,小烈需要走一个来回,如图:
-
本来,小烈可以按 \(1, 2,3...n\) 的顺序一次给每个顾客上菜,但是,聪明的小烈通过观察发现,每个顾客都有一个开心值 \(H_1, H_2, H_3…,H_n\) ,离厨房最近的为 \(H_1\),然后依次为 \(H_2,H_3…,H_n\) 。若小烈给第 \(j\) 位顾客上菜前刚刚为第 \(i\) 位顾客上菜,则第 \(j\) 位就会高兴,产生高兴指数 \(W_j=H_i×H_j\) 。这样,如果小烈按一定的方式调整上菜顺序,可以得到更高的高兴指数。现在小烈想知道用某一方法可达到的 \(n\) 位顾客高兴指数之和的最大值\(S\)。因为顾客越高兴,给小烈的小费越多。第一位上菜的顾客不产生高兴值。
Input
- 第一行一个整数 \(n\),顾客的数目。
- 第二行 \(n\) 个数,第 \(i\) 个数表示第 \(i\) 位顾客的开心值。各个数字用空格隔开。
Output
- 一个数 \(s\),为高兴指数的最大值。
Sample Input
3
7 1 9
Sample Output
72
Hint
- 样例解释:从左往右上 \(1\) 的菜,再上 \(9\) 的菜,高兴值是 \(0*1+1*9\),从右往左走回来的时候上 \(7\) 的菜,高兴值是 \(7*9\),总的高兴值就是 \(72\)。
- 对于 \(30\%\) 的数据 \(n≤9,n∈N^+\);
- 对于 \(70\%\) 的数据 \(n≤1500,n∈N^+\);
- 对于\(100\%\) 的数据 \(n≤2500,n∈N^+\) ;
- 所有数字小于(含结果)\(2147483648\) ;
分析
-
这道题用动态规划解决的要点在于如何设计出恰当的状态。
-
定义 \(f[i][j]\) 表示去程走到 \(i\),回程走到 \(j\) 时可以取得的最高开心指数,但这种状态的定义不能确定在 \(i\) 之前的位置有哪些位置是尚未送菜的,同样的问题也出现在 \(j\) 之后的位置中。这就好像 我们才做的 \(NOIP08\) 的《方格取数》,如果两个进程的起点不一样,我们无法确定哪个方格已经被取过了,哪个还没有。
-
这就启发我们将题目所描述的情形变成两个小烈同时从左边往右边走,容易看出,这样转化是等价的。同时,我们需要注意的是每个位置都必须送到,而且只能有两个小烈里面的其中一个送到。这样考虑之后,状态之间的转移关系就很显然了。
-
\(f[i][j]\) 表示小烈 \(a\) 和小烈 \(b\) 分别走到了 \(i,j\) 位置,同时记 \(m=max(x1,x2)\),对于小于 \(m\) 的每一个位置都已经被送到。那么,在 \(f[i][j]\) 这一状态下,需要决定第 \(m+1\) 个人由谁来送菜。显然这是一个多阶段决策问题,\(m\) 就是阶段的标识。可以用动态规划求解。
-
同时,可以发现,在第 \(m\) 个阶段通过决策转移到 \(m+1\) 个阶段是,每一个决定(小烈 \(a\) 来送
还是小烈 \(b\) 来送)都对应一个状态为:\(f[m+1][j]\) 和 \(f[i][m+1]\) 。
-
因为在送的过程中,每个位置都需要送到,而且两个小烈都是一样的,所以,\(f[i][j]==f[j][i]\) 。这个性质很重要,大家一定要好好领悟领悟。
-
这样,我们让小烈 \(a\) 始终在前面,小烈 \(b\) 在后面,即,\(f[i][j]\) 保证 \(i>j\) 。
-
转移方程:
- \(f[i+1][j]=max(f[i+1][j],f[i][j]+a[i]*a[i+1])\) ,\(i+1\) 由 \(i\) 转移过来
- \(f[i+1][i]=max(f[i+1][i],f[i][j]+a[j]*a[i+1])\),\(i+1\) 由 \(j\) 转移过来,此时另外一个小烈必然在\(i\) 比较难理解,一定要先理解红色的地方,然后保证第一维大于第二维,品,仔细品,老姚品了好久才品过味来,你们一定比老姚强!
Code
#include <bits/stdc++.h>
const int maxn=2500+5;
int a[maxn],f[maxn][maxn];
void Solve(){
int n , ans = 0;
scanf("%d",&n);
for(int i = 1; i <= n; i++)
scanf("%d",&a[i]);
for(int i = 1; i <= n; i++){//前面的小烈
for(int j = 0; j < i; j++){//后面的小烈
f[i+1][j] = std::max(f[i+1][j], f[i][j] + a[i] * a[i+1]);
f[i+1][i] = std::max(f[i+1][i], f[i][j] + a[j] * a[i+1]);
}
}
for(int i = 0; i < n; i++)
ans = std::max(ans, f[n][i] + a[n] * a[i]);
//这里,小烈a已经走到头,小烈b可能在0到n-1之间的任何位置
//两个小烈必须汇合才能表示走完了一圈,所以要加上a[n] * a[i]
printf("%d\n",ans);
}
int main(){
Solve();
return 0;
}