您的位置:首页 > 其它

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

2014-09-25 22:04 453 查看
题意是给你一串数和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;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: