【洛谷】【前缀和+st表】P2629 好消息,坏消息

【题目描述:】

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)

【输出格式:】

一行一个整数,表示可行的方案个数。

在旁边wzx大(巨)佬的指点下切掉了这道题



[算法分析:]

对于要判断区间\([l,\ r]\)内在某一时刻的和是否为负值,只需要看从\([l,\ r]\)前缀和序列的值是否为负.

(这道题是要求对于一个点\(i\),判断\([i,\ n]\)\([1,\ i-1]\)中某一时刻的前缀和是否存在负值)

那怎样快速地求出这个前缀和序列并判断是否有负值呢?

枚举的方法显然并不高效,反而和直接模拟的复杂度一样了,抛掉

判断是否有负值,可以找到一个标准,只要这个标准非负,那整个序列也就没有负值了

显然的,标准应选为\([l,\ r]\)前缀和序列的最小值,便可以使用st表或者线段树来快速查询.

那又应该如何求出\([l,\ r]\)前缀和序列呢?每次求一遍?显然复杂度会降到约为\(O(n^3)\)

对于一个序列\(a\)\([1,\ r]\)前缀和序列的最小值再减去\([1,\ l - 1]\)的元素和,即为\([l,\ r]\)前缀和序列最小值

即:$$min{\sum_{j = l}^{r}\sum_{i = l}^{j} a_i} = min{\sum_{j = 1}^{r}\sum_{i = 1}^{j} a_i} - \sum_{i = 1}^{l - 1}a_i$$

所以当我们根据序列\(a\)求出\([l,\ r]\)的前缀和序列sum时,使用st表维护这个序列sum的最小值,

就可以做到\(O(1)\)查询\(min\{\sum_{j = l}^{r}\sum_{i = l}^{j} a_i\}\)

预处理复杂度\(O(nlog_2n)\),算法主体的时间复杂度为\(O(n)\)

而使用线段树也可以维护最小值,根据前缀和序列建树,预处理\(O(n)\),算法主体复杂度为\(O(nlog_2n)\),和st表差不多。

(但是实测好像st表跑的慢一些)

也可以使用单调队列来求最值,复杂度应该是\(O(n)\),比st表和线段树都要快,但是我不会QwQ



\([Code:]\)

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<queue>
using namespace std;

const int MAXN = 1e6 + 1;
const int K = 20 + 1;

int n;
int a[MAXN], sum[MAXN];
int st[MAXN][K], Log2[MAXN];

inline int read() {
    int x=0, f=1; char ch=getchar();
    while(ch<'0' || ch>'9') {
        if(ch == '-') f = -1;
        ch = getchar();
    }
    while(ch>='0' && ch<='9')
        x=(x<<3)+(x<<1)+ch-48, ch=getchar();
    return x * f;
}

inline int Query(int l, int r) {
    int x = Log2[r - l + 1];
    return min(st[l][x], st[r-(1<<x)+1][x]);
}

int main() {
    n = read();
    for(int i=1; i<=n; ++i) a[i] = read();
    for(int i=1; i<=n; ++i)
        st[i][0] = sum[i] = sum[i-1] + a[i];
    for(int j=1; j<=K; ++j)
        for(int i=1; i+(1<<j)-1<=n; ++i)
            st[i][j] = min(st[i][j-1], st[i+(1<<(j-1))][j-1]);
    for(int i=0; (1<<i)<=n; ++i) Log2[1<<i] = i;
    for(int i=1; i<=n; ++i)
        if(!Log2[i]) Log2[i] = Log2[i-1];
    int ans = 0;
    for(int i=1; i<=n; ++i) {
        if(Query(i, n)-sum[i-1] < 0) continue;
        if(Query(1, i-1) + sum[n] - sum[i-1] >= 0) ++ans;
    }
    printf("%d\n", ans);
}
posted @ 2018-05-23 19:02  DEVILK  阅读(262)  评论(0编辑  收藏  举报