B. Array Walk
原题链接:传送门
题目大意
给定我们一个数组,对于位置x你每次可以执行两种操作。
1.向右移动一个单位,x = x + 1
2.向左移动一个单位,x = x - 1
但是有规定,你一共可以执行 k 次操作,且这 k 次操作种操作2做多只能执行 z 次 , 且操作2不能被连续执行。
分析
解法一 : 动态规划方法
一开始思考用贪心的思路去解决,但是没有想到合适的贪心策略,于是作罢。后来开始往dp方面想。
状态表示:
$f(i , q) $表示当前位置 i 并且使用了 q 次左移的最大价值
状态计算:
对于当前位置 i , 如果我们当前使用 q 次想左边移动的机会。那么
(f(i , q) = max(f(i , q) , f(i-1,q - j) + s + a[i]))
那么对于公式种的j 和 s是怎么回事呢
j 表示在最多右移q次的情况下当前移动了j次 (0le j le q)
由于我们只能向左移不超过1次,那么我们一定是在位置 i / i - 1 之间不停摇摆
对于q - j :
假设我们当前为(f(i , 3))
那么其一定可以由以下三种情况转移过来
- 1.(f(i-1 , 0) + 3 * (a[i] + a[i-1])) 第前i - 1个位置上使用了 0 次左移
- 2.(f(i-1,1) + 2 * (a[i] + a[i-1]))第前i - 1个位置上使用了 1 次左移
- 3.(f(i-1,2) + 1 * (a[i] + a[i-1]))第前i - 1个位置上使用了 2 次左移
- 4.(f(i-1,3) + 0 * (a[i] + a[i-1]))第前i - 1个位置上使用了 3 次左移
(f(i-1 , q - j))表示在位置 i 有q次机会左移的情况从第i - 1种状态的第q - j 种转移过来
一开始公式中的(s = j * (a[i] + a[i-1])) 表示向左移动 j 次所能获得的收益
注意事项
AC 代码
C++ code
const int N = 100005;
int test;
int dp[N][6];
void slove()
{
memset(dp , 0 , sizeof dp);
int n , k , z;
cin >> n >> k >> z;
vector<int> a(n + 1);
for(int i = 1;i <= n ;i ++)cin >> a[i];
int ans = 0;
for(int i = 2;i <= n ;i ++)
{
dp[i][0] = dp[i-1][0] + a[i];
if(i - 1 == k)ans = max(ans , dp[i][0]);
for(int q = 1;q <= z ;q ++) // 枚举向左走了q次
for(int j = 0;j <= q ;j ++)
{
int s = j * (a[i] + a[i-1]);
dp[i][q] = max(dp[i][q] , dp[i-1][q-j] + s + a[i]);
// 如果恰好落在 i 这个位置上且机会已经用完则更新答案
if(i - 1 + q * 2 == k)ans = max(ans , dp[i][q]);
// 如果落在了i - 1的位置上且机会已经用完更新答案
if(i - 1 + q * 2 - 1 == k)ans = max(ans , dp[i][q]-a[i]);
}
}
cout << ans + a[1] << endl;// 因为一开始站在 a[1]
}
解法二:贪心方法
贪心策略
对于一个贪心问题,最重要的莫过于贪心策略的选择,即每次只贪婪的选择当下对自己最有利的情况。
我们需要求出对于每一个位置不停的向左再向右移动所能够获取的最大价值。
code
void slove2()
{
int n , k , z;
cin >> n >> k >> z;
int sum = 0 , ans = 0 , mx = 0;
k ++ ;vector<int> a(n + 1);
for(int i = 1;i <= n ;i ++)cin >> a[i];
for(int i = 1;i <= k ;i ++)
{
sum += a[i];
if(i < n)mx = max(mx , a[i] + a[i + 1]);
// 将剩下的次数变成往右走之后反复横跳
int t = (k - i) / 2;
ans = max(ans , min(t , z) * mx + sum);
}
cout << ans << endl;
}