奶牛的跳格子游戏

前言

我也没弄懂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[i1]pre[j+1]

状态转移的证明

可以参考这张图(虽然很丑)

在这里插入图片描述

我们假设走到 iii 后开始回去的第一步是 jjj

红色部分的含义(状态转移中 dp[i]dp[i]dp[i]dp[j]dp[j]dp[j] 之间的关系):

条件

一: jjj 满足条件 i−j<=xi - j <= xij<=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 < iij<=x,k<i

不等式缩放可知:

k−j<=xk - j <= xkj<=x

即证

四引理:由于我们可以随意在 (j + 1, i)中选点,所以我们为了 moneymoneymoney 最多,所以肯定选择正数。

暴力枚举返回时走的第一步,暴力转移就行了,只要注意几个特殊情况:

一:直接回到 老家 000 号点.

二:返回时的第一步为 i−1i - 1i1

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;
}

我们考虑优化

假设有两个决策点 jjjkkk

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[i1]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[i1]pre[j+1]>a[i]+a[k+1]+dp[k]+pre[i1]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]=max⁡ji−x<=j<i−1w(j)dp[i] = \max_{j}^{i - x <= j < i - 1}{w (j)}dp[i]=maxjix<=j<i1w(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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值