您的位置:首页 > 其它

[NOIP2017模拟]最佳序列

2017-11-04 13:51 337 查看
2017.11.2 T2 2029

样例数据

输入

3 2 3

6 2 8

输出

5.3333

分析:这道题我只会扫描所有的区间输出答案这种暴力,也就是说复杂度是O((r−l)∗N),复杂度与区间有关,结果数据水,40%成功水成90%,而且如果进行优化,还能AC!

正解是单调队列优化:

二分平均数,将每个数都减去一个平均数,然后记录前缀和,如果在长为l到r的所有区间中有前缀和大于0的说明就可以到这个平均数,没有就说明不能到平均数,单调队列能让这个过程变成O(N)的。

代码

100%:暴力+优化

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<string>
#include<ctime>
#include<cmath>
#include<algorithm>
#include<cctype>
#include<iomanip>
#include<queue>
#include<set>
using namespace std;

inline int getint()
{
int sum=0,f=1;
char ch;
for(ch=getchar();!isdigit(ch)&&ch!='-';ch=getchar());
if(ch=='-')
{
f=-1;
ch=getchar();
}
for(;isdigit(ch);ch=getchar())
sum=(sum<<3)+(sum<<1)+ch-48;
return sum*f;
}

const int maxn=20005;
const double eps=1e-9;
int n,l,r,a[maxn],maxa;
double ans,sum[maxn];

inline void solve()
{
for(register int i=l;i<=min(l*2,r);++i)//这是个优化
//如果是一个数的区间,显然取最大的那个数就行,它和任意数取平均数都会比本身小
//同理,如果找到了长为l的平均最大的区间,显然与其他长为l的区间合并求平均数都会比他小,所以只需要求长为l到长为2l的区间的最大值就完了
//现在想来觉得有点小bug啊......
{
if(sum[i]/i-ans>eps)
ans=sum[i]/i;
for(register int j=i+1;j<=n;++j)
if((sum[j]-sum[j-i])/i-ans>eps)
ans=(sum[j]-sum[j-i])/i;
}
printf("%0.4f\n",ans);
}

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

n=getint(),l=getint(),r=getint();
for(register int i=1;i<=n;++i)
{
a[i]=getint();
sum[i]=sum[i-1]+a[i];//记录前缀和
}

solve();
return 0;
}


100%:单调队列优化

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<string>
#include<ctime>
#include<cmath>
#include<algorithm>
#include<cctype>
#include<iomanip>
#include<deque>
#include<set>
using namespace std;

int getint()
{
int sum=0,f=1;
char ch;
for(ch=getchar();!isdigit(ch)&&ch!='-';ch=getchar());
if(ch=='-')
{
f=-1;
ch=getchar();
}
for(;isdigit(ch);ch=getchar())
sum=(sum<<3)+(sum<<1)+ch-48;
return sum*f;
}

const int maxn=20010;
const double eps=1e-9;
int n,l,r;
double w,ans,a[maxn],maxa,b[maxn],sum[maxn];

bool check(double x)
{
for(int i=1;i<=n;++i) b[i]=a[i]-x;//把所有的数减去二分的这个平均数
for(int i=1;i<=n;++i) sum[i]=sum[i-1]+b[i];//记录前缀和
deque<int> que;
for(int i=l;i<=r-1;++i)//先把长为l到r-1的前缀和放到数组里(只能是单调队列,小于的都删掉)
{
while(!que.empty()&&sum[i]>sum[que.back()])
que.pop_back();
que.push_back(i);
}

for(int i=1;i<=n-l+1;++i)//固定左端点
{
while(!que.empty()&&que.front()<i+l-1)
que.pop_front();//删除队顶与左端点相距小于的l的点(可能队列中还有,但是单调队列只关注最大的),保证队顶的数是满足在左端点右侧l到r的距离内的
if(i+r-1<=n)
{
while(!que.empty()&&sum[i+r-1]>sum[que.back()])//加入新的,把小于它的都删掉
que.pop_back();
que.push_back(i+r-1);
}
if(sum[que.front()]-sum[i-1]>=0)//判断队列中(队列中的都是满足在区间里的)最大的能否让前缀和大于0,满足了就return true
return true;
}
return false;
}

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

n=getint(),l=getint(),r=getint();
for(int i=1;i<=n;++i)
{
scanf("%lf",&a[i]);
if(a[i]-maxa>eps)
maxa=a[i];
}

double l=0,r=maxa,mid;
while(r-l>eps)//二分查找平均数
{
mid=(l+r)/2;
if(check(mid))
l=mid,ans=mid;
else
r=mid;
}
printf("%0.4f\n",ans);
return 0;
}


本题结。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: