cf1611 F. ATM and Students(双指针,二分,ST表)

题意:

给定非负初值 s 和长为 n 的数组 a[]a[] 中元素可正可负。求一段最长的区间 [l~r] 使得 \(\forall j\in [l,r],s+\sum_{i=l}^j \ge 0\) ,即 s 加上 a[l~r] 的任意前缀都大于等于0。输出lr

思路:

法一:双指针

每次循环中,r 右移到满足条件的最右点,尝试用 [l,r] 更新答案,然后l 右移一步。

难点:为什么当 l 右移时,r 也只需要右移?

假设 r 尽量右移后, [l,r] 合法(即 s+a[l]+a[l+1]+...+a[r]>=0),那么 [l,r+1] 不合法(即 s+a[l]+a[l+1]+...+a[r]+a[r+1]<0),所以 a[r+1]<0

l-r+1 更新答案。接下来让 l+1

如果 [l+1,r] 不合法(即 s+a[l+1]+...+a[r]<0),那么s+a[l+1]+...+a[r]+a[r+1]<0r不会右移。此时 [l+1,r] 的长度比 [l,r] 小,不会影响答案。

如果 [l+1,r] 合法(即 s+a[l+1]+...+a[r]>=0),若s+a[l+1]+...+a[r]+a[r+1]<0r不会右移,不影响答案;若s+a[l+1]+...+a[r]+a[r+1]>=0,那么 [l+1,r+1] 合法,r 右移,更新答案。

#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const int N = 2e5 + 5;
int n, s, a[N], ans, ansl, ansr;

signed main()
{
    int T; scanf("%d", &T); while(T--)
    {
        scanf("%d%d", &n, &s);
        for(int i = 1; i <= n; i++) scanf("%d", &a[i]);

        ans = 0; //找第一个合法元素作为区间起点
        for(int i = 1, ok = 0; !ok && i <= n; i++) if(s + a[i] >= 0)
            ansl = ansr = i, ans = 1, ok = 1;
        if(!ans) {puts("-1"); continue; }

        for(ll l=ansl, r=ansl, res = s+a[ansl]; l <= n; res-=a[l++])
        {
            if(r < l) res += a[++r];
            while(r + 1 <= n && res >= 0 && res + a[r+1] >= 0)
                res += a[++r]; //r若合法就尽量右移
            if(ans < r - l + 1) ans = r - l + 1, ansl = l, ansr = r;
        }
        printf("%d %d\n", ansl, ansr);
    }

    return 0;
}

法二:二分答案,ST表维护前缀和的区间最小值

二分答案x。对每个左端点l,用ST表找[l,l+x-1]中前缀和的最小值y。如果这个最小的y都满足 s+y-a[l-1]>=0[l,l+x-1] 合法,于是 x 合法。

注意二分是找满足条件的最大答案

#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const int N = 2e5 + 5;
int n, s, ansl, ansr;
ll a[N], f[N][21];
void initST()
{
    for(int i = 1; i <= n; i++) f[i][0] = a[i];
    int t = log(n) / log(2) + 1;
    for(int j = 1; j < t; j++)
        for(int i = 1; i <= n - (1<<j) + 1; i++)
            f[i][j] = min(f[i][j-1], f[i+(1<<(j-1))][j-1]);
}
ll ask(int l, int r)
{
    int k = log(r - l + 1) / log(2);
    return min(f[l][k], f[r - (1<<k) + 1][k]);
}

bool ok(int x)
{
    for(int l = 1; l + x - 1 <= n; l++)
    {
        int r = l + x - 1;
        if(s + ask(l, r) - a[l-1] >= 0)
        {
            ansl = l, ansr = r;
            return 1;
        }
    }
    return 0;
}

signed main()
{
    int T; cin >> T; while(T--)
    {
        scanf("%d%d", &n, &s);
        for(int i = 1; i <= n; i++)
            scanf("%lld", &a[i]), a[i] += a[i-1];

        initST();

        int l = 0, r = 2e5;
        while(l < r)
        {
            int mid = l + r + 1 >> 1;
            if(ok(mid)) l = mid;
            else r = mid - 1;
        }
        if(l == 0) puts("-1");
        else printf("%d %d\n", ansl, ansr);
    }

    return 0;
}

posted @ 2021-12-15 14:18  Bellala  阅读(134)  评论(0)    收藏  举报