【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
    评论
NOIP(全国青少年信息学奥林匹克竞赛)是中国国内最高水平的信息学竞赛之一,是计算机领域的重要赛事。针对NOIP模拟题,通常是为了帮助参赛者熟悉比赛形式和题型,提供足够的训练机会。 数据下载是NOIP比赛中的一个重要环节,因为参赛者需要根据提供的数据集进行程序开发和测试。数据下载一般通过网络进行,参赛者需要在指定的时间段内下载数据集到自己的计算机上。 在进行NOIP模拟题数据下载时,我们可以按照以下步骤进行操作: 1. 确认下载链接:NOIP官方会提供下载数据集的链接或者FTP地址,参赛者需要确认链接是否可用和正确。 2. 选择下载工具:根据自己的需求,参赛者可以选择合适的下载工具进行操作。常见的下载工具有浏览器内置下载工具、迅雷、IDM等,可以根据个人的习惯和需求选择合适的下载工具。 3. 打开下载工具:根据所选择的下载工具类型,打开对应的软件,进入下载界面。 4. 输入下载链接:将NOIP提供的数据集下载链接复制粘贴到下载工具的链接输入框中,点击确定或开始进行下载。 5. 等待下载完成:根据数据集的大小和网络速度不同,下载时间会有所变化。参赛者需要耐心等待下载完成,确保数据集完整地保存到自己的计算机上。 6. 验证数据完整性:下载完成后,参赛者需要验证数据集的完整性,确保所有文件都成功地保存到指定位置。可以进行文件大小的比对或者逐个文件的校验来检查数据完整性。 通过以上步骤,参赛者可以成功地进行NOIP模拟题数据的下载。在实际比赛中,一个高效的数据下载过程可以提高参赛者的准备时间和竞争力,确保能够充分利用所提供的数据集进行开发和测试。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值