奶牛的跳格子游戏

前言

我也没弄懂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[i1]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 ij<=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 ij<=x,k<i

不等式缩放可知:

k − j < = x k - j <= x kj<=x

即证

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

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

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

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

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[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 ] + 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[i1]pre[j+1]>a[i]+a[k+1]+dp[k]+pre[i1]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]=maxjix<=j<i1w(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;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值