单调队列 最小前缀和 T3 https://vjudge.csgrandeur.cn/problem/%E6%B4%9B%E8%B0%B7-P2629
题目描述
Uim 在公司里面当秘书,现在有 条消息要告知老板。每条消息有一个好坏度,这会影响老板的心情。告知完一条消息后,老板的心情等于老板之前的心情加上这条消息的好坏度。最开始老板的心情是 ,一旦老板心情到了 以下就会勃然大怒,炒了 Uim 的鱿鱼。
Uim 为了不被炒,提前知道了这些消息(已经按时间的发生顺序进行了排列)的好坏度,希望知道如何才能不让老板发怒。
Uim 必须按照事件的发生顺序逐条将消息告知给老板。不过 Uim 可以使用一种叫 “倒叙” 的手法,例如有 条消息,Uim 可以按 (事件编号)这种顺序通报。
他希望知道,有多少个 ,可以使从 号事件开始通报到 号事件然后再从 号事件通报到 号事件可以让老板不发怒。
输入格式
第一行一个整数 (),表示有 个消息。
第二行 个整数,按时间顺序给出第 条消息的好坏度 ()。
输出格式
一行一个整数,表示可行的方案个数。
样例 #1
样例输入 #1
4
-3 5 1 2
样例输出 #1
2
提示
【样例解释】
通报事件的可行顺序(用编号表示)为 或 (分别对应 和 )
通报事件的可行顺序(用好坏度表示)为 或
【数据范围】
对于 的数据,;
对于 的数据,;
对于 的数据,。
思路
TSP旅行商问题的简化版, 先断环成链, 把数组复制一份,对应样例 -3 5 1 2 -3 5 1 2。
题目要求是在 (循环数组中)确保不会出现一个最小前缀和其值小于0, 即 。因此我们可以用单调队列维护区间内最小的 , 如果最小的都是非负数, 那么整个区间就成立。
接下来就是循环枚举 即 , 同时维护 区间内的最小值作为 , 如果存在 (多减一个1才是区间 的和), 则标记 为 FALSE, 表示该 值不成立。 最后再统计一下 为 TRUE 的数量, 或者直接不用 数组, 当成立时累加答案也可以。
因为我们是直接拷贝了一份数组, 故当 时候才去更新对应的 。
为什么这里跟旅行商问题的计算不太一样呢? 同样都是求最小前缀和, 旅行商是 单调队列维护 的最大值, 而这里是 , 维护的是 的最小值。可以发现, 前者是维护减数, 后者是维护被减数。
而并不能直接套用旅行商问题的方法, 因为这里是顺时针计算, 而旅行商中 维护最大的 时是逆时针计算。
旅行商的顺时针计算可以带入到这道题, 逆序枚举 , 维护 的最小值 , 同样是 。不过更容易理解些。注意需要先插入队尾再更新答案, 且队列初始时不加入0, 因为求得是最小值。
代码
#include <bits/stdc++.h>
using namespace std;
const int N = 3e6;
int st[N], s[N], q[N];
void solve()
{
int n;
cin >> n;
for (int i = 1; i <= n; i++)
{
cin >> s[i];
s[i + n] = s[i];
}
for (int i = 1; i <= 2 * n; i++)
s[i] += s[i - 1];
int hh = 0, tt = -1;
for (int i = n * 2; i; i--)
{
if (hh <= tt && q[hh] >= i + n)
hh++;
while (hh <= tt && s[q[tt]] >= s[i])
tt--;
q[++tt] = i;
if (i <= n)
if (s[q[hh]] - s[i - 1] >= 0)
st[i] = true;
}
int res = 0;
for (int i = 1; i <= n; i++)
{
if (st[i])
res++;
}
cout << res << endl;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(nullptr);
solve();
return 0;
}