BZOJ5090 组题 BZOJ2017年11月月赛 二分答案 单调队列

欢迎访问~原文出处——博客园-zhouzhendong

去博客园看该题解


题目传送门 - BZOJ5090 11月月赛A题


题意概括

  给出n个数。

  求连续区间(长度大于等于k)最大平均值。


题解

  这题大概不是原题。

  很简单的题目(对于大佬而不对于我来说),做过一次。

  具体做法:

  首先二分答案平均值(最好用long double保证精度)

  然后根据前缀和来单调队列判断。

  假设当前要判断的答案为x。

  我们把原序列的每一个数都减去x。

  那么前缀和数组的第i个就减掉了i*x

  那么我得到了一个新的前缀和数组(long double型)。

  如果原序列存在平均值大于x的,那么修改后的序列必然存在总和大于等于0的连续一段数(当然长度要大于或等于k)

  那么只需要看是否有距离>=k的两个前缀和的值满足前面一个小于后面一个就可以了。

  这个可以用单调队列维护解决。

  剩下的就是答案及细节处理。注意开longlong


代码

#include <cstring>
#include <algorithm>
#include <cstdio>
#include <cstdlib>
#include <cmath>
using namespace std;
typedef long long LL;
typedef long double LD;
const int N=100005;
const LD Eps=1e-9;
int n,k,ansL,ansR;
int q[N],head,tail;
LL sum[N],A,B;
LD val[N];
LL gcd(LL a,LL b){
	return b?gcd(b,a%b):a;
}
bool check(LD x){
	for (int i=0;i<=n;i++)
		val[i]=-x*i+sum[i];
	head=1,tail=0;
	for (int i=k;i<=n;i++){
		while (head<=tail&&val[i-k]<val[q[tail]])
			tail--;
		q[++tail]=i-k;
		if (head<=tail&&val[q[head]]<val[i]){
			ansL=q[head],ansR=i;
			return 1;
		}
	}
	return 0;
}
int main(){
	scanf("%d%d",&n,&k);
	sum[0]=0;
	for (int i=1;i<=n;i++)
		scanf("%lld",&sum[i]),sum[i]+=sum[i-1];
	LD L=-1e8,R=1e8,M;
	while (R-L>Eps){
		M=(R+L)*0.5;
		if (check(M))
			L=M;
		else
			R=M;
	}
	A=sum[ansR]-sum[ansL];
	B=ansR-ansL;
	LL g=gcd(A,B);
	A/=g,B/=g;
	if (B<0)
		A=-A,B=-B;
	printf("%lld/%lld",A,B);
	return 0;
}

  

posted @ 2017-12-08 21:29  zzd233  阅读(211)  评论(0编辑  收藏  举报