题目描述
uim 在公司里面当秘书,现在有 n 条消息要告知老板。每条消息有一个好坏度,这会影响老板的心情。
告知完一条消息后,老板的心情等于之前老板的心情加上这条消息的好坏度。
最开始老板的心情是 (0) ,一旦老板心情到了 (0) 以下就会勃然大怒,炒了 uim 的鱿鱼。
uim 为了不被炒,知道了了这些消息(已经按时间的发生顺序进行了排列)的好坏度,希望研究如何不让老板发怒。
uim 必须按照时间的发生顺序逐条将消息告知给老板。不过 uim 可以使用一种叫“倒叙”的手法,例如有n条消息,小a可以从 (k,k+1,k+2...n,1,2...k-1) 这种顺序通报。
他希望知道,有多少个 (k) ,从 (k) 开始通报到 (n) 然后从 (1) 通报到 (k-1) 可以让老板不发怒。
输入格式
第一行一个整数 (n(1 <= n <= 10^6)) ,表示有 (n)个消息。
第二行 (n) 个整数,按时间顺序给出第i条消息的好坏度 (Ai (-1000 <= Ai <= 1000))
输出格式
一行一个整数,表示可行的方案个数。
输入输出样例
输入 #1
4
-3 5 1 2
输出 #1
2
说明/提示
样例解释
([5 1 2 -3]) 或 ([1 2 -3 5])
对于 (25\%) 数据 (n<=1000)
对于 (75\%) 数据 (n<=10000)
对于 (100\%) 数据 (n<=10^6)
————————————————————————————————
对于这道题,显然 (k) 有 (n) 种可能的情况,我们只要对每个 (k) 判断是否合法即可
但如果暴力枚举每一个k,时间复杂度是 (O(n^2))的,只有 (25) 分,需要考虑优化
在考虑优化前,我们考虑 拆环为链 的简化方法,即把样例的 (-3;5;1;2) 补充成为 (-3;5;1;2;-3;5;1)
这样对于每一个起点 (k),我们只需要考虑往后的元素了,把两段的汇报合并成了一段长度为 (n) 的完整汇报
然后我们考虑 老板发怒 的状态是什么,即从 (k) 开始向后统计前缀和,前缀和出现负值的情况则会发怒
于是题目就转换为了:有多少个 (k),从 (k) 开始逐个统计长度为 (1,2,..,n) 的前缀和,这些前缀和都值都不是负数
在实际处理中,我们可以首先计算出完整序列的前缀和 (sum),(sum[i]-sum[k-1]) 就是 (k o i(k<=i<=n+k-1)) 的上述前缀和了
由于我们只关心最小的那个 (sum[i]-sum[k-1]),我们可以使用一个单调队列来维护 (sum[i]),时间复杂度为 (O(n))
对于每一段区间的最小值,最后要再减去 (sum[k-1]) 观察是否为负值,如果不为复制,则说明这个 (k) 是合法的
代码如下
感觉这份代码比较难懂,因为我自己写的时候一直在细节上改233 还是请关注解题思路。
#include <bits/stdc++.h>
#define MAXN 2000007
using namespace std;
int n,ans,sum[MAXN],f[MAXN];
deque<int> Q; vector<int> v;
inline int readint() {
int X=0,w=0; char ch=0;
while (!isdigit(ch)) w|=ch=='-',ch=getchar();
while (isdigit(ch)) X=(X<<3)+(X<<1)+(ch^48),ch=getchar();
return w?-X:X;
}
int main() {
n=readint();
for (int i=1;i<=n;i++) v.push_back(readint());
for (int i=0;i<n-1;i++) v.push_back(v[i]);
for (int i=0;i<(int)v.size();i++) sum[i]=(i?sum[i-1]:0)+v[i];
for (int i=0;i<(int)v.size();i++) {
if (!Q.empty() && Q.front()+n<=i) Q.pop_front();
while (!Q.empty() && sum[i]<=sum[Q.back()])
Q.pop_back();
Q.push_back(i);
if (i-n+1>=0) f[i-n+1]=sum[Q.front()]-((i==n-1)?0:sum[i-n]);
}
for (int i=0;i<n;i++) if (f[i]>=0) ans++;
printf("%d",ans);
return 0;
}