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

本文介绍了一种算法,用于确定消息通报顺序,确保老板心情始终非负,避免因坏消息导致的心情跌至负值而引发的解雇风险。通过前缀和及数据结构优化,实现高效查询。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

【题目描述:】

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);
}

转载于:https://www.cnblogs.com/devilk-sjj/p/9078732.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值