小烈送菜——奇怪的dp
小烈送菜
题目描述
小烈一下碰碰车就被乐满地的工作人员抓住了。作为扰乱秩序的惩罚,小烈必须去乐满地里的“漓江村”饭店端盘子。
服务员的工作很繁忙。他们要上菜,同时要使顾客们尽量高兴。一位服务生为 \(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\times H_j\) 。这样,如果小烈按一定的方式调整上菜顺序,可以得到更高的高兴指数。现在小烈想知道用某一方法可达到的 \(n\) 位顾客高兴指数之和的最大值 \(S\) 。因为顾客越高兴,给小烈的小费越多。第一位上菜的顾客不产生高兴值。
输入格式
第一行一个整数 \(n\) ,顾客的数目。
第二行 \(n\) 个数,第 \(i\) 个数表示第 \(i\) 位顾客的开心值。各个数字用空格隔开。
输出格式
一个数 \(s\) ,为高兴指数的最大值。
样例
样例输入
3
7 1 9
样例输出
72
数据范围与提示
样例解释:从左往右上 \(1\) 的菜,再上 \(9\) 的菜,高兴值是 \(0\times 1+1\times 9\) ,从右往左走回来的时候上 \(7\) 的菜,高兴值是 \(7\times 9\) ,总的高兴值就是 \(72\) 。
对于 \(30\%\) 的数据\(n\leq9,n\in N^+\);
对于 \(70\%\) 的数据\(n\leq1500,n\in N^+\);
对于 \(100\%\) 的数据\(n\leq2500,n\in N^+\);
所有数字小于(含结果) \(2147483648\) ;
思路
题意中的来回两次直接 \(dp\) 的话,还需要记录第一次走的路径,很麻烦。
根据之前的方格取数,我们也可以把这个题转换成两个小烈送菜。
定义 \(dp[i][j]\) 为有两个小烈同时从厨房开始送菜,第一个小烈到达 \(i\) 的位置,第二个小烈到达 \(j\) 的位置,所能取到的最大值。
有一个很重要的性质:\(dp[i][j]=dp[j][i]\)。
第一个小烈送到 \(i\) 的位置,第二个小烈送到 \(j\) 的位置与第一个小烈送到 \(j\) 的位置,第二个小烈送到 \(i\) 的位置所得的结果是相同的。
动态转移方程:
第一个小烈由 \(i\) 送到 \(i+1\) 的位置。
\(dp[i+1][j]=max(dp[i+1][j],dp[i][j]+a[i]*a[i+1])\)
或者第二个小烈由 \(j\) 送到 \(i+1\) 的位置。
\(dp[i][i+1]=max(dp[i][i+1],dp[i][j]+a[j]*a[i+1])\)
根据之前的性质,可以把转移方程转化为:
\(dp[i+1][i]=max(dp[i+1][i],dp[i][j]+a[j]*a[i+1])\)
这样保证了 \(dp[i][j]\) 中的 \(i\) 一定会大于 \(j\),便于处理。
最后在寻找一遍最大值即可。
代码
#include <bits/stdc++.h>
using namespace std;
const int maxn=2500+50;
int n,ans;
int a[maxn];
int dp[maxn][maxn];
int main(){
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++){
dp[i+1][j]=max(dp[i+1][j],dp[i][j]+a[i]*a[i+1]);//前面那个处于i位置的小烈送i+1的菜
dp[i+1][i]=max(dp[i+1][i],dp[i][j]+a[j]*a[i+1]);//后面那个处于j位置的小烈送i+1的菜,并根据性质翻转
}
}
for(int i=0;i<n;i++){
ans=max(ans,dp[n][i]+a[n]*a[i]);//前面那个小烈送到n的位置,但是不确定后面那个小烈在哪里,所以以此枚举
}
cout<<ans<<endl;
return 0;
}