题目链接
题目概括:
给定一个序列v,每次可以从左端点处或右端点处取走一个数v[i],第a次取数可获得的价值为v[i]*a,求把这个序列取完可获得的最大价值
分析
要想获得最大价值,肯定要让大的数字后取。
做法
1.贪心(27分)
用双指针枚举首和尾,看哪个小就先取哪个。
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
const int N = 2010;
int hh, tt, a, v[N], n, ans;
int main() {
scanf("%d", &n);
hh = 1;
for(tt = 1; tt <= n ; tt ++)
scanf("%d", &v[tt]);
tt = n;
// cout << endl;
while(hh <= tt) {
if(v[hh] < v[tt]) {
ans += v[hh] * ++a;
hh ++;
// cout << ans << endl;
}
else{
ans += v[tt] * ++a;
tt --;
// cout << ans << endl;
}
}
cout << ans << endl;
return 0;
}
2.正解:区间DP
建议先看注释,注释很清楚。
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
const int N = 2010;
int n, v[N], f[N][N];
int main() {
cin >> n;
for(int i = 1; i <= n ; i ++)
scanf("%d", &v[i]);
for(int i = 1; i <= n ; i ++)
f[i][i] = v[i] * n;
for(int len = 2; len <= n ; len ++) {
for(int i = 1; i <= n ; i ++) {
int j = i + len - 1;
f[i][j] = max(f[i+1][j]+v[i]*(n - len + 1), f[i][j-1]+v[j]*(n - len + 1));
}
}
/*
f[i][j]表示区间[i, j]全都取完,可获得的最大价值。
f[i][j]可以由两个方向转移过来
一个是1.f[i+1][j]
另一个2.f[i][j-1]
在如上两种取法可获得的最大价值中取max,再加上由1或2转移到f[i][j]可获得的价值,就是f[i][j]
边界:当区间长度(j - i + 1) 为 1 时也就是i == j 时,可获得的价值是v[i] * n
值得一提:
由1或2转移到f[i][j]时,可获得的价值是v[i]*a或v[j]*a,但是这里的a用什么来表示呢?
当转移到的区间长度(len)为n时,a = 1;
当转移到的区间长度(len)为n-1时,a = 2;
... ...
当转移到的区间长度(len)为2时,a = n - 1;
当转移到的区间长度(len)为1时,a = n
这时候我们可以发现:
a + len 始终是等于n + 1的
∵ a + len == n + 1
∴ a == n - len + 1
代码实现:
根据区间dp的普遍写法,先初始化边界(i:1 to n, f[i][i] = v[i] * n)
然后枚举区间长度len
枚举左端点i
算出右端点j = i + len - 1
并转移f[i][j] = max(f[i+1][j]+v[i]*(n - len +1), f[i][j-1]+v[j]*(n-len+1))
最后的答案就是f[1][n](从第1个数到第n个数全部取完,可获得的最大价值)
*/
cout << f[1][n] << endl;
return 0;
}
OI生涯中第一道区间DP
2020.11.4
写于初中OI退役前第3天