题目描述
小蓝制作了
n
n
n 个蛋糕并将其从左往右排成一行,其中第
i
i
i 个蛋糕的饱腹度为
w
i
w_i
wi 其可口值为
d
i
d_i
di 。
由于手法过于生疏,尽管每个蛋糕的饱腹度必然为正数,但是可能存在蛋糕的可口值为负数!
作为可口蛋糕大赛的评委,小灰灰需要吃掉一段连续的蛋糕,使得蛋糕的饱腹度之和至少为
W
W
W
而小蓝的得分就是小灰灰吃掉蛋糕所对应的可口值之和,她想知道在小灰灰帮助她的情况下,她的最大可能得分是多少。
输入描述:
第一行两个空格分隔的整数分别代表
n
n
n 和
W
W
W。接下来一行
n
n
n 个空格分隔的整数分别代表:
w
1
,
w
2
,
.
.
.
,
w
n
w_1,w_2,...,w_n
w1,w2,...,wn。
再接下来一行
n
n
n 个空格分隔的整数分别代表:
d
1
,
d
2
,
.
.
.
,
d
n
d_1,d_2,...,d_n
d1,d2,...,dn。保证:
1
≤
n
≤
1
0
6
1\leq n\leq10^6
1≤n≤106
1
≤
W
,
w
i
≤
1
0
9
1\leq W,w_i\leq10^9
1≤W,wi≤109
0
≤
∣
d
i
∣
≤
1
0
9
0\leq|d_i|\leq10^9
0≤∣di∣≤109
W
≤
∑
i
=
1
n
w
i
W\leq\sum_{i=1}^nw_i
W≤∑i=1nwi
输出描述:
输出一行一个整数代表答案。
示例1:
输入
5 8
1 4 5 2 3
-1 -1 1 -2 1
输出
0
分析:
我们知道饱腹度必然是正数,而可口度可能是负数,我们可以枚举当前节点i,找到饱腹度开始大于W的pos,那么pos后面的饱腹度必然也大于W,那么这时只需要寻找在pos之后可口度的最大值,如果直接暴力查找,时间复杂度是
O
(
n
2
)
O(n^2)
O(n2)的会超时,想想怎么优化,可以用一个md数组,记录从该节点之后的最大可口度,这样我们就能以
O
(
1
)
O(1)
O(1)的复杂度找到最大可口度。
下面是完整代码
#include<iostream>
#include<algorithm>
#define ll long long
using namespace std;
const int N = 1e6 + 10; // 定义常量N为1e6 + 10,这是题目中给出的蛋糕数量的上限
ll n, w; // n表示蛋糕的数量,w表示小灰灰要求的最小饱腹度
ll sumw[N], sumd[N], md[N]; // sumw数组存储蛋糕的饱腹度前缀和,sumd数组存储蛋糕的可口值前缀和,md数组用于存储从当前位置到末尾最大可口值
int main(){
cin >> n >> w;
for(int i = 1; i <= n; i++){
int x;
cin >> x;
sumw[i] = x + sumw[i-1]; // 计算饱腹度的前缀和
}
for(int i = 1; i <= n; i++){
int x;
cin >> x;
sumd[i] = x + sumd[i-1]; // 计算可口值的前缀和
}
// md数组初始化为一个非常小的值(这里使用了-1e15)
md[n+1] = -1e15;
for(int i = n; i >= 1; i--){
md[i] = max(md[i+1], sumd[i]); // 从后向前,维护当前位置i到末尾的最大可口值
}
ll res = -1e15; // 结果初始化为一个非常小的值(这里使用了-1e15)
for(int i = 1; i <= n; i++){
// 使用lower_bound找到第一个使得饱腹度之和至少为w的蛋糕位置,注意这里是sum[i-1]不是sum[i]
int pos = lower_bound(sumw+1, sumw+n+1, sumw[i-1]+w) - sumw;
// 更新结果,res为吃掉的蛋糕的可口值之和的最大值
res = max(res, md[pos] - sumd[i-1]);
}
cout << res << endl; // 输出结果
return 0;
}
补充:
for(int i = n;i>=1;i–){
md[i] = max(md[i+1],sumd[i]);
}
这个为什么要后序遍历
原因是:这里使用后序遍历(从后往前遍历)是为了建立一个从当前位置 i 到末尾的最大可口值数组 md。因为我们想要知道对于任意位置 i,从 i 到 n 这一段蛋糕中可口值之和的最大值是多少。这样当我们在后面寻找最大得分时,只需要查看 md[i] 就能快速得到从位置 i 开始到末尾的最大可口值之和。而用前序遍历的话明显要找的不是从当前位置 i 到末尾的最大可口值数。