【NOIP模拟】 (11.2) T2最佳序列

20 篇文章 1 订阅
18 篇文章 0 订阅

最佳序列

题目描述:
       N个数,从中选择长度不小于L且不大于R的连续子序列,求出子序列平均数的最大值。

输入格式:
       输入文件的第一行包括3个整数N,L,R。
       第二行包括N个数,按顺序依次表示序列A的每一项。

输出格式:
       输出文件包括一行,一个实数,保留四位小数。

数据范围:
       20%的数据满足:1<=N<=200;R=N。
       40%的数据满足:1<=N<=2000。
       100%的数据满足:1<=n<=20000;1<=L<=R<=N;0<=Ai<=1000000

解析:
       对于20%的数据,暴力枚举即可。
       对于40%的数据,可以用前缀和的方式降维(但这道题在暴力进行优化后可以拿满......)。
       对于100%的数据,二分答案是很好想到的,但这道题的关键在于如何验证。于是我们要进行推理:
       假设这时我们二分到的平均数为x,那么如果满足条件,在这个序列中一定有一个长度为L~R的子序列满足平均数大于等于x,如果用暴力搜索肯定超时,所以我们来思考一下若当前搜索到了i作为右端点,左端点一定在i-R+1~i-L+1这个区间里,如果满足条件,那么
       sum[i]-sum[i-R]>=(i-R)*x,sum[i]-sum[i-L]>=(i-L)*x(sum为前缀和)
       我们把等式右边拆开移到左边就变成了
       sum[i]-i*x-(sum[i-R]-R*x)>=0,右边的不等式同理
       拆开后相当于
       sum[i]'-sum[i-R]'>=0,右边的不等式同理
       所以我们只用将sum都减去x,再计算出的sum[i-R]~sum[i-L]中如果最小的sum[j]能使得不等式成立,说明满足提议,x是存在的,否则是不存在的。那么问题来了,如何求出在一个长度固定的区间求最小的值,这里出现了两种方法:一种是线段树,一种是单调队列。
       线段树很好想到,因为这个问题是一个很裸的模板题,将sum减去x后存进线段树,然后枚举i时进行查找最小值即可。时间复杂度O(N (log N)^2)。
       这道题能用单调队列的原因是满足长度固定,而且每次取最小值,满足单调性,所以可以用单调队列实现。时间复杂度O(N log N)

代码(暴力100分....)
#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cstdlib>
#include <cmath>
#include <cctype>
#include <cstring>
#include <string>
#include <vector>
#include <queue>
#include <ctime>
#include <sstream>
using namespace std;

const int Max=200010;
int n,L,R;
double l,r,mid;
double ans;
long long num[Max],sum[Max];

inline int get_int()
{
   int x=0,f=1;
   char c;
   for(c=getchar();(!isdigit(c))&&(c!='-');c=getchar());
   if(c=='-') {f=-1;c=getchar();}
   for(;isdigit(c);c=getchar()) x=(x<<3)+(x<<1)+c-'0';
   return x*f;
}

inline int check()
{
   for(register int i=L;i<=R;i++)
   {
   	 long long maxx=0;
     for(register int j=i;j<=n;j++)
       maxx=max(sum[j]-sum[j-i],maxx);
     ans=max((double)(maxx)/(double)(i),ans);
   }
}

int main()
{
   //freopen("seq.in","r",stdin);
   //freopen("seq.out","w",stdout);

   n=get_int();
   L=get_int();
   R=get_int();
   for(register int i=1;i<=n;i++) num[i]=get_int();
   for(register int i=1;i<=n;i++) sum[i]=sum[i-1]+num[i];
   l=0;
   r=1e7;
   check();
   printf("%0.4f\n",ans);
   return 0;
}

代码(二分+单调队列):
#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cstdlib>
#include <cmath>
#include <cctype>
#include <cstring>
#include <string>
#include <vector>
#include <queue>
#include <ctime>
#include <sstream>
using namespace std;

const int Max=200010;
int n,L,R;
double l,r,mid;
int maxx;
int num[Max];
double sum[Max];

inline int get_int()
{
   int x=0,f=1;
   char c;
   for(c=getchar();(!isdigit(c))&&(c!='-');c=getchar());
   if(c=='-') {f=-1;c=getchar();}
   for(;isdigit(c);c=getchar()) x=(x<<3)+(x<<1)+c-'0';
   return x*f;
}

inline int check(double x)
{
   static int p[Max];
   for(int i=1;i<=n;i++) sum[i]=(double)num[i]-x;
   for(int i=1;i<=n;i++) sum[i]+=sum[i-1];
   int head=1,tail=0;
   for(int i=L;i<=n;i++)         //注意要从L开始而不能从L+1开始,不然会漏算1~L的情况
   {
   	 while(head<=tail&&sum[p[tail]]>=sum[i-L]) tail--;
   	 p[++tail]=i-L;
   	 while(head<=tail&&i-R>p[head]) head++;
   	 if(sum[p[head]]<=sum[i]) return 1;
   }
   return 0;
}

int main()
{
   //freopen("seq.in","r",stdin);
  // freopen("seq.out","w",stdout);

   n=get_int();
   L=get_int();
   R=get_int();
   for(int i=1;i<=n;i++) {num[i]=get_int();maxx=max(maxx,num[i]);}
   l=0;
   r=maxx;
   for(int i=1;i<=50;i++)
   {
   	 mid=(l+r)/2;
   	 if(check(mid)) l=mid;
   	 else r=mid;
   }
   printf("%0.4f\n",l);
   return 0;
}

代码(二分+线段树)
#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cstdlib>
#include <cmath>
#include <cctype>
#include <cstring>
#include <string>
#include <vector>
#include <queue>
#include <ctime>
#include <sstream>
using namespace std;

const int MAX=1e9;
const int Max=200010;
int n,L,R;
double l,r,mid;
int maxx;
int num[Max];
double sum[Max];
struct shu{int l,r;double minn;};
shu tree[Max*4];

inline int get_int()
{
   int x=0,f=1;
   char c;
   for(c=getchar();(!isdigit(c))&&(c!='-');c=getchar());
   if(c=='-') {f=-1;c=getchar();}
   for(;isdigit(c);c=getchar()) x=(x<<3)+(x<<1)+c-'0';
   return x*f;
}

inline void build(int root,int L,int R)
{
   tree[root].l=L;
   tree[root].r=R;
   if(L==R)
   {
   	 tree[root].minn=sum[L];
   	 return;
   }
   int mid=(L+R)>>1;
   build(root<<1,L,mid);
   build(root<<1|1,mid+1,R);
   tree[root].minn=min(tree[root<<1].minn,tree[root<<1|1].minn);
}

inline double Q(int root,int L,int R,int l,int r)
{
   if(l>R||r<L) return MAX;
   if(l<=L&&r>=R)
   {
   	 return tree[root].minn;
   }
   int mid=(L+R)>>1;
   if(r<=mid) return Q(root<<1,L,mid,l,r);
   else if(l>mid) return Q(root<<1|1,mid+1,R,l,r);
   else return min(Q(root<<1,L,mid,l,r),Q(root<<1|1,mid+1,R,l,r));
}

inline int check(double x)
{
   memset(tree,0,sizeof(tree));
   static int p[Max];
   for(register int i=1;i<=n;i++) sum[i]=(double)num[i]-x;
   for(register int i=1;i<=n;i++) sum[i]+=sum[i-1];
   build(1,0,n);
   for(register int i=L+1;i<=n;i++)
   {
   	 if(Q(1,0,n,max(i-R,0),i-L)<=sum[i]) return 1;
   }
   return 0;
}

int main()
{
   //freopen("seq.in","r",stdin);
  // freopen("seq.out","w",stdout);

   n=get_int();
   L=get_int();
   R=get_int();
   for(register int i=1;i<=n;i++) {num[i]=get_int();maxx=max(maxx,num[i]);}
   l=0;
   r=maxx;
   for(register int i=1;i<=50;i++)
   {
   	 mid=(l+r)/2;
   	 if(check(mid)) l=mid;
   	 else r=mid;
   }
   printf("%0.4f\n",l);
   return 0;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值