奶牛的跳格子游戏
前言
我也没弄懂TJ的意思
样例分析图
题解部分
题面略;
1.状态: d p [ i ] dp[i] dp[i],表示从 0 0 0 开始走到 i i i 再走回去所得的钱的最大值
2.转移
状态转移方程
记 p r e [ i ] pre[i] pre[i] 表示 ∑ j = 1 i a [ j ] ( a [ j ] > 0 ) \sum_{j = 1}^{i} a[j](a[j] > 0) ∑j=1ia[j](a[j]>0)
则 d p [ i ] = a [ i ] + a [ j + 1 ] + d p [ j ] + p r e [ i − 1 ] − p r e [ j + 1 ] dp[i] = a[i] + a[j + 1] + dp[j] + pre[i - 1] - pre[j + 1] dp[i]=a[i]+a[j+1]+dp[j]+pre[i−1]−pre[j+1]
状态转移的证明
可以参考这张图(虽然很丑)
我们假设走到 i i i 后开始回去的第一步是 j j j
红色部分的含义(状态转移中 d p [ i ] dp[i] dp[i] 和 d p [ j ] dp[j] dp[j] 之间的关系):
条件
一: j j j 满足条件 i − j < = x i - j <= x i−j<=x。
二: j + 1 j + 1 j+1 一定是我们走过的,所以我们必须选择 a [ j + 1 ] a[j + 1] a[j+1]。
三:由于我们走到了 i i i, 所以我们必须选择 a [ i ] a[i] a[i]。
四: j + 1 j + 1 j+1出发后走到的任何一个位置(不超过 i i i)都满足要求(即可以选择任意一个不超过 i i i 的点)
我们由图,我们从 j j j 走到了 j + 1 j + 1 j+1,假设下一步走到的位置是 k k k
因为: i − j < = x , k < i i - j <= x, k < i i−j<=x,k<i
不等式缩放可知:
k − j < = x k - j <= x k−j<=x
即证
四引理:由于我们可以随意在 (j + 1, i)中选点,所以我们为了 m o n e y money money 最多,所以肯定选择正数。
暴力枚举返回时走的第一步,暴力转移就行了,只要注意几个特殊情况:
一:直接回到 老家
0
0
0 号点.
二:返回时的第一步为 i − 1 i - 1 i−1
O ( n 2 ) O (n ^ 2) O(n2) 暴力代码
#include <map>
#include <set>
#include <cmath>
#include <stack>
#include <queue>
#include <vector>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
#define int long long
#define LL long long
#define ULL unsigned long long
template <typename T> int read (T &x) {x = 0; T f = 1;char tem = getchar ();while (tem < '0' || tem > '9') {if (tem == '-') f = -1;tem = getchar ();}while (tem >= '0' && tem <= '9') {x = (x << 1) + (x << 3) + tem - '0';tem = getchar ();}x *= f; return 1;}
template <typename T> void write (T x) {if (x < 0) {x = -x;putchar ('-');}if (x > 9) write (x / 10);putchar (x % 10 + '0');}
template <typename T> T Max (T x, T y) { return x > y ? x : y; }
template <typename T> T Min (T x, T y) { return x < y ? x : y; }
template <typename T> T Abs (T x) { return x > 0 ? x : -x; }
const int Maxn = 25 * 1e4;
const LL Inf = 1e18;
int n, x;
LL a[Maxn + 5], pre[Maxn + 5], dp[Maxn + 5];
LL Answer (int l, int r) {
if (l <= r)
return pre[r] - pre[l - 1];
else
return 0;
}
signed main () {
scanf ("%lld %lld", &n, &x);
for (int i = 1; i <= n; i++) {
scanf ("%lld", &a[i]);
pre[i] = pre[i - 1];
if (a[i] > 0) pre[i] += a[i];
}
LL ans = 0;
for (int i = 1; i <= n; i++) {
dp[i] = -Inf;
if (i <= x) dp[i] = Max (dp[i], a[i] + Answer (1, i - 1));
dp[i] = Max (dp[i], a[i] + dp[i - 1]);
for (int j = Max ((LL)0, i - x); j < i - 1; j++) {
dp[i] = Max (dp[i], a[i] + a[j + 1] + dp[j] + Answer (j + 2, i - 1));
}
ans = Max (ans, dp[i]);
}
printf ("%lld", ans);
return 0;
}
我们考虑优化
假设有两个决策点 j j j 和 k k k
令 w ( j , i ) = a [ i ] + a [ j + 1 ] + d p [ j ] + p r e [ i − 1 ] − p r e [ j + 1 ] w (j, i) = a[i] + a[j + 1] + dp[j] + pre[i - 1] - pre[j + 1] w(j,i)=a[i]+a[j+1]+dp[j]+pre[i−1]−pre[j+1]
有这样一个结论:
若 w ( j , i ) > w ( k , i ) w(j, i) > w (k, i) w(j,i)>w(k,i), 则 w ( j , i + 1 ) > w ( k , i + 1 ) w (j, i + 1) > w (k, i + 1) w(j,i+1)>w(k,i+1)
其实这个证明真的非常简单,但担心有人说我水题解,我还是写一些吧
由题意得:
a [ i ] + a [ j + 1 ] + d p [ j ] + p r e [ i − 1 ] − p r e [ j + 1 ] > a [ i ] + a [ k + 1 ] + d p [ k ] + p r e [ i − 1 ] − p r e [ k + 1 ] a[i] + a[j + 1] + dp[j] + pre[i - 1] - pre[j + 1] > a[i] + a[k + 1] + dp[k] + pre[i - 1] - pre[k + 1] a[i]+a[j+1]+dp[j]+pre[i−1]−pre[j+1]>a[i]+a[k+1]+dp[k]+pre[i−1]−pre[k+1]
则:
a [ j + 1 ] + d p [ j ] − p r e [ j + 1 ] > a [ k + 1 ] + d p [ k ] − p r e [ k + 1 ] a[j + 1] + dp[j] - pre[j + 1] > a[k + 1] + dp[k] - pre[k + 1] a[j+1]+dp[j]−pre[j+1]>a[k+1]+dp[k]−pre[k+1]
所以:
a [ i + 1 ] + a [ j + 1 ] + d p [ j ] + p r e [ i ] − p r e [ j + 1 ] > a [ i + 1 ] + a [ k + 1 ] + d p [ k ] + p r e [ i ] − p r e [ k + 1 ] a[i + 1] + a[j + 1] + dp[j] +pre[i] - pre[j + 1] > a[i + 1] + a[k + 1] + dp[k] + pre[i] - pre[k + 1] a[i+1]+a[j+1]+dp[j]+pre[i]−pre[j+1]>a[i+1]+a[k+1]+dp[k]+pre[i]−pre[k+1]
又因为 d p [ i ] = max j i − x < = j < i − 1 w ( j ) dp[i] = \max_{j}^{i - x <= j < i - 1}{w (j)} dp[i]=maxji−x<=j<i−1w(j).
所以我们可以用单调队列维护一个下降的 w ( j ) w (j) w(j) 就可以啦。
之前未写证明的 S B SB SB 代码,有点冗长
时间复杂度: O(n)
#include <map>
#include <set>
#include <cmath>
#include <stack>
#include <queue>
#include <vector>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
#define int long long
#define LL long long
#define ULL unsigned long long
template <typename T> int read (T &x) {x = 0; T f = 1;char tem = getchar ();while (tem < '0' || tem > '9') {if (tem == '-') f = -1;tem = getchar ();}while (tem >= '0' && tem <= '9') {x = (x << 1) + (x << 3) + tem - '0';tem = getchar ();}x *= f; return 1;}
template <typename T> void write (T x) {if (x < 0) {x = -x;putchar ('-');}if (x > 9) write (x / 10);putchar (x % 10 + '0');}
template <typename T> T Max (T x, T y) { return x > y ? x : y; }
template <typename T> T Min (T x, T y) { return x < y ? x : y; }
template <typename T> T Abs (T x) { return x > 0 ? x : -x; }
const int Maxn = 25 * 1e4;
const LL Inf = 1e18;
int n, x;
LL a[Maxn + 5], pre[Maxn + 5], dp[Maxn + 5];
LL Answer (int l, int r) {
if (l <= r)
return pre[r] - pre[l - 1];
else
return 0;
}
int Get_Val (int j, int i) {
return a[j + 1] + dp[j] + Answer (j + 2, i - 1);
}
int hh = 1, tt = 0, q[Maxn + 5];
void add (int j, int i) {
while (hh <= tt && Get_Val (q[tt], i) <= Get_Val (j, i)) {
tt--;
}
q[++tt] = j;
}
signed main () {
scanf ("%lld %lld", &n, &x);
for (int i = 1; i <= n; i++) {
scanf ("%lld", &a[i]);
pre[i] = pre[i - 1];
if (a[i] > 0) pre[i] += a[i];
}
LL ans = 0;
for (int i = 1; i <= n; i++) {
dp[i] = -Inf;
if (i <= x) dp[i] = Max (dp[i], a[i] + Answer (1, i - 1));
dp[i] = Max (dp[i], a[i] + dp[i - 1]);
while (hh <= tt && q[hh] < i - x) hh++;
if (hh < tt) dp[i] = Max (dp[i], a[i] + Get_Val (q[hh], i));
add (i - 1, i + 1);
ans = Max (ans, dp[i]);
}
printf ("%lld", ans);
return 0;
}
写了证明后,发现有些东西可以约掉,维护的单调队列的对象更加简洁了。
#include <map>
#include <set>
#include <cmath>
#include <stack>
#include <queue>
#include <vector>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
#define int long long
#define LL long long
#define ULL unsigned long long
template <typename T> int read (T &x) {x = 0; T f = 1;char tem = getchar ();while (tem < '0' || tem > '9') {if (tem == '-') f = -1;tem = getchar ();}while (tem >= '0' && tem <= '9') {x = (x << 1) + (x << 3) + tem - '0';tem = getchar ();}x *= f; return 1;}
template <typename T> void write (T x) {if (x < 0) {x = -x;putchar ('-');}if (x > 9) write (x / 10);putchar (x % 10 + '0');}
template <typename T> T Max (T x, T y) { return x > y ? x : y; }
template <typename T> T Min (T x, T y) { return x < y ? x : y; }
template <typename T> T Abs (T x) { return x > 0 ? x : -x; }
const int Maxn = 25 * 1e4;
const LL Inf = 1e18;
int n, x;
LL a[Maxn + 5], pre[Maxn + 5], dp[Maxn + 5];
LL Answer (int l, int r) {
if (l <= r)
return pre[r] - pre[l - 1];
else
return 0;
}
int Get_Val (int j) {
return a[j + 1] + dp[j];
}
int hh = 1, tt = 0, q[Maxn + 5];
void add (int j) {
while (hh <= tt && Get_Val (q[tt]) + Answer (q[tt] + 2, j + 1) <= Get_Val (j)) {
tt--;
}
q[++tt] = j;
}
signed main () {
scanf ("%lld %lld", &n, &x);
for (int i = 1; i <= n; i++) {
scanf ("%lld", &a[i]);
pre[i] = pre[i - 1];
if (a[i] > 0) pre[i] += a[i];
}
LL ans = 0;
for (int i = 1; i <= n; i++) {
dp[i] = -Inf;
if (i <= x) dp[i] = Max (dp[i], a[i] + Answer (1, i - 1));
dp[i] = Max (dp[i], a[i] + dp[i - 1]);
while (hh <= tt && q[hh] < i - x) hh++;
if (hh < tt) dp[i] = Max (dp[i], a[i] + a[q[hh] + 1] + dp[q[hh]] + Answer (q[hh] + 2, i - 1));
add (i - 1);
ans = Max (ans, dp[i]);
}
printf ("%lld", ans);
return 0;
}