HOJ 13006 Minimal Subarray Length (单调队列或RMQ加二分)

题意是给你一串数和X,要求序列和>=x中长度最短是多少。

不得不说此题数据太水,优化点的暴力都能过,比如可以通过o(n)的方法求出每个数以自己为结尾的序列最大值是多少,然后从后往前扫,只要这个值>=x,就往加找直到>=x的最小值,下一个>=x的加到的之前算出的最短长度的位置还不>=x就不用加了,这样的确能过,还挺快,但一组数据完破

500000 250000

下面500000个1,答案是250000,显而易见。

但是用这个做法的复杂度就是o(250000^2)级别了。要跑很久很久很久很久。。

先说用RMQ加二分的做法。

这个做法挺容易理解,RMQ是预处理区间前缀和的最大值。复杂度o(nlogn)

然后枚举起始点,前缀和没有单调性但是可以用区间最大值来二分。可以找到最靠近起始点且>=x的地方。

总复杂度o(nlogn),但是跑的比较慢。

AC代码:

#include<cstdio>
#include<ctype.h>
#include<algorithm>
#include<iostream>
#include<cstring>
#include<vector>
#include<cstdlib>
#include<stack>
#include<cmath>
#include<queue>
#include<set>
#include<map>
#include<ctime>
#include<string.h>
#include<string>
using namespace std;
#define ll __int64
#define eps 1e-10
#define MOD 10007
typedef int state[21];
template<class T>
inline void scan_d(T &ret)
{
    char c;
    int flag = 0;
    ret=0;
    while(((c=getchar())<'0'||c>'9')&&c!='-');
    if(c == '-')
    {
        flag = 1;
        c = getchar();
    }
    while(c>='0'&&c<='9') ret=ret*10+(c-'0'),c=getchar();
    if(flag) ret = -ret;
}
ll a[500005];
ll sum[500005];
int dp[500005][20];
void RMQ_init(int n)
{
    for(int i = 1; i <= n; i++) dp[i][0] = sum[i];
    for(int j = 1; (1<<j) <= n; j++)
        for(int i = 0; i + (1<<j) - 1 < n; i++)
            dp[i][j] = max(dp[i][j-1],dp[i+(1<<(j-1))][j-1]);
}

int RMQ(int l,int r)
{
    int k = log2(r-l+1.0);
    return max(dp[l][k],dp[r-(1<<k)+1][k]);
}

int main()
{
#ifdef GLQ
    freopen("input.txt","r",stdin);
//    freopen("o4.txt","w",stdout);
#endif // GLQ
    int t,n,x,i,j;
    scanf("%d",&t);
    while(t--)
    {
        scanf("%d%d",&n,&x);
        for(i = 1; i <= n; i++)
        {
            scanf("%I64d",&a[i]);
            sum[i] = sum[i-1]+a[i];
        }
        RMQ_init(n);
        int ans = n+1;
        for(i = 1; i <= n; i++)
        {
            int l=i,r = n;
            while(l<r)
            {
                int m = l+(r-l)/2;
                if(RMQ(l,m) - sum[i-1] >= x) r = m;
                else l = m+1;
            }
            if(sum[r] - sum[i-1] >= x) ans = min(ans,r-i+1);
        }
        if(ans == n+1) printf("-1\n");
        else printf("%d\n",ans);
    }
    return 0;
}

第二种做法是单调队列

这个有点难理解,我看了别人的代码研究了半天才弄懂。

因为是要求sum[j]-sum[i] >= x,所以最好sum[i]尽量小,如果对于i之前的某个sum都比sum[i]大了,那么相同的sum[j]去减之前的那个,即大小比减sum[i]小,距离也比j到i要大,所以这个就该不要了。
但是注意,这个在x是负数的时候会出问题。数据太水!!没有注意这个问题也能A。

比如这组数据

8 -2
-1 -4 -3 -4 -1 -1 -3 -1

答案明显是1,但是如果用我刚才的算法,就会出现不断的去更新因为sum越来越小,导致得不出结论。

所以需要特判一下,特判很简单,如果X<=0,如果序列里没有一个是大于等于X的,那么区间和必定得不出X。

这个代码的时间复杂度是o(n),分析一下,首先rear++的次数有N次,也就是说rear最大也就是N+1,同时rear>=1那么第一个while最多也只能有n次,然后front是不会变小的,然后front最大就是N,所以while也最多n次,所以总复杂度就是o(n)。其实跟kmp那个复杂度分析很像。

AC代码:

#include<cstdio>
#include<ctype.h>
#include<algorithm>
#include<iostream>
#include<cstring>
#include<vector>
#include<cstdlib>
#include<stack>
#include<cmath>
#include<queue>
#include<set>
#include<map>
#include<ctime>
#include<string.h>
#include<string>
using namespace std;
#define ll __int64
#define eps 1e-10
#define MOD 10007
typedef int state[21];
template<class T>
inline void scan_d(T &ret)
{
    char c;
    int flag = 0;
    ret=0;
    while(((c=getchar())<'0'||c>'9')&&c!='-');
    if(c == '-')
    {
        flag = 1;
        c = getchar();
    }
    while(c>='0'&&c<='9') ret=ret*10+(c-'0'),c=getchar();
    if(flag) ret = -ret;
}
ll a[500005];
ll sum[500005];
int que[500005];
int main()
{
#ifdef GLQ
    freopen("input.txt","r",stdin);
//    freopen("o4.txt","w",stdout);
#endif // GLQ
    int t,n,x,i,j;
    scanf("%d",&t);
    while(t--)
    {
        scanf("%d%d",&n,&x);
        int flag = 0;
        for(i = 1; i <= n; i++)
        {
            scan_d(a[i]);
            if(!flag && a[i] >= x) flag = 1;
            sum[i] = sum[i-1]+a[i];
        }
        if(flag)
        {
            printf("1\n");
            continue;
        }
        if(x <= 0)
        {
            printf("-1\n");
            continue;
        }
        int front = 0,rear = 1;
        que[0] = 0;
        int ans = n+1;
        for(i = 1; i <= n; i++)
        {
            while(front < rear && sum[i] <= sum[que[rear-1]]) rear--;
            que[rear++] = i;
            while(front < rear-1 && sum[i] - sum[que[front]] >= x)
            {
                ans = min(ans,i-que[front]);
                front++;
            }
        }
        if(ans == n+1) printf("-1\n");
        else printf("%d\n",ans);
    }
    return 0;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值