前言
我也没弄懂TJ的意思
样例分析图
题解部分
题面略;
1.状态:dp[i]dp[i]dp[i],表示从 000 开始走到 iii 再走回去所得的钱的最大值
2.转移
状态转移方程
记 pre[i]pre[i]pre[i] 表示 ∑j=1ia[j](a[j]>0)\sum_{j = 1}^{i} a[j](a[j] > 0)∑j=1ia[j](a[j]>0)
则 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]dp[i]=a[i]+a[j+1]+dp[j]+pre[i−1]−pre[j+1]
状态转移的证明
可以参考这张图(虽然很丑)
我们假设走到 iii 后开始回去的第一步是 jjj
红色部分的含义(状态转移中 dp[i]dp[i]dp[i] 和 dp[j]dp[j]dp[j] 之间的关系):
条件
一: jjj 满足条件 i−j<=xi - j <= xi−j<=x。
二:j+1j + 1j+1 一定是我们走过的,所以我们必须选择 a[j+1]a[j + 1]a[j+1]。
三:由于我们走到了 iii, 所以我们必须选择 a[i]a[i]a[i]。
四:j+1j + 1j+1出发后走到的任何一个位置(不超过 iii)都满足要求(即可以选择任意一个不超过 iii 的点)
我们由图,我们从 jjj 走到了 j+1j + 1j+1,假设下一步走到的位置是 kkk
因为: i−j<=x,k<ii - j <= x, k < ii−j<=x,k<i
不等式缩放可知:
k−j<=xk - j <= xk−j<=x
即证
四引理:由于我们可以随意在 (j + 1, i)中选点,所以我们为了 moneymoneymoney 最多,所以肯定选择正数。
暴力枚举返回时走的第一步,暴力转移就行了,只要注意几个特殊情况:
一:直接回到 老家 000 号点.
二:返回时的第一步为 i−1i - 1i−1
O(n2)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;
}
我们考虑优化
假设有两个决策点 jjj 和 kkk
令 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)=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]+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[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]+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[j+1]+dp[j]−pre[j+1]>a[k+1]+dp[k]−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]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]
又因为 dp[i]=maxji−x<=j<i−1w(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) 就可以啦。
之前未写证明的 SBSBSB 代码,有点冗长
时间复杂度: 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;
}