bzoj 5090: 组题 凸包+二分

题意

著名出题人小Q的备忘录上共有n道可以出的题目,按照顺序依次编号为1到n,其中第i道题目的难度系数被小Q估计为a_i,难度系数越高,题目越难,负数表示这道题目非常简单。小Q现在要出一套难题,他决定从备忘录中选取编号连续的若干道题目,使得平均难度系数最高。当然,小Q不能做得太过分,一套题目必须至少包含k道题目,因此他不能通过直接选取难度系数最高的那道题目来组成一套题。请写一个程序,帮助小Q挑选平均难度系数最高的题目。
输出一个既约分数p/q或-p/q,即平均难度系数的最大值。
1<=n<=100000,1<=k<=n,|a_i|<=10^8

分析

实际就是要求一个数对(i,j),满足i>j且i-j>k,并且(s[i]-s[j])/(i-j)最大。
注意到这就是一个斜率的形式,直接维护一个凸包然后在凸包上二分即可。
一开始wa是因为在gcd的时候没有考虑到会出现负数。

代码

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#include<cmath>
using namespace std;

typedef long long LL;

const int N=100005;

int n,k,q[N];
LL s[N];

int read()
{
    int x=0,f=1;char ch=getchar();
    while (ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while (ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
    return x*f;
}

LL gcd(LL x,LL y)
{
    if (!y) return x;
    else return gcd(y,x%y);
}

int main()
{
    freopen("a.in","r",stdin);
    n=read();k=read();
    for (int i=1;i<=n;i++) s[i]=(LL)read()+s[i-1];
    LL x=s[k],y=k;
    int head=1,tail=1;q[1]=0;
    for (int i=1;i<=n-k;i++)
    {
        while (head<tail&&(LL)(s[i]-s[q[tail]])*(q[tail]-q[tail-1])<=(LL)(s[q[tail]]-s[q[tail-1]])*(i-q[tail])) tail--;
        q[++tail]=i;
        int l=head,r=tail-1;
        while (l<=r)
        {
            int mid=(l+r)/2;
            if ((LL)(s[i+k]-s[q[mid]])*(i+k-q[mid+1])>(LL)(s[i+k]-s[q[mid+1]])*(i+k-q[mid])) r=mid-1;
            else l=mid+1;
        }
        if ((LL)(s[i+k]-s[q[r+1]])*y>(LL)x*(i+k-q[r+1])) x=s[i+k]-s[q[r+1]],y=i+k-q[r+1];
    }
    LL d=gcd(x,y);if (d<0) d=-d;
    printf("%lld/%lld",x/d,y/d);
    return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值