【bzoj5090】组题 分数规划

题目描述

给出一个长度为n的序列,求一段长度大于等于k的字串,使得它们的平均值最大。

输入

第一行包含两个整数n,k(1<=n<=100000,1<=k<=n),分别表示题目的总量和题数的下界。

第二行包含n个整数a_1,a_2,...,a_n(|a_i|<=10^8),分别表示每道题目的难度系数。

输出

输出一个既约分数p/q或-p/q,即平均难度系数的最大值。

样例输入

5 3
1 4 -2 -3 6

样例输出

5/4


题解

分数规划

二分答案mid,将每个数减去mid后,问题转化为判定性问题:是否存在一个长度大于等于k的字串,使得它们的和非负。

把区间和转化为前缀相减的形式,求出前缀和。枚举区间右端点 $i$ ,要判定的就是 $sum[i]-min(sum[j])(0\le j\le i-k)$ 是否大于等于0。存在某一个大于等于0则有解,否则无解。

但是答案询问的是分数形式怎么办?只需要每次记录答案的选择方式,最后根据这个方式再计算一遍即可。

时间复杂度 $O(n\log n)$ 

需要注意的一点是答案可能为负数,因此取gcd时需要取区间和的绝对值计算。

#include <cstdio>
#include <cstring>
#include <algorithm>
#define N 100010
using namespace std;
typedef long long ll;
int a[N] , n , k , ansl , ansr;
double sum[N];
ll gcd(ll a , ll b)
{
    return b ? gcd(b , a % b) : a;
}
bool judge(double mid)
{
    int i , pos = 0;
    for(i = 1 ; i <= n ; i ++ ) sum[i] = sum[i - 1] + a[i] - mid;
    for(i = k ; i <= n ; i ++ )
    {
        if(sum[i] - sum[pos] >= 0)
        {
            ansl = pos + 1 , ansr = i;
            return 1;
        }
        if(sum[i - k + 1] < sum[pos]) pos = i - k + 1;
    }
    return 0;
}
int main()
{
    int i , cnt = 40;
    ll p = 0 , q , t;
    double l = 1 << 30 , r = -1 << 30 , mid;
    scanf("%d%d" , &n , &k);
    for(i = 1 ; i <= n ; i ++ )
    {
        scanf("%d" , &a[i]);
        l = min(l , 1.0 * a[i]) , r = max(r , 1.0 * a[i]);
    }
    while(cnt -- )
    {
        mid = (l + r) / 2;
        if(judge(mid)) l = mid;
        else r = mid;
    }
    q = ansr - ansl + 1;
    for(i = ansl ; i <= ansr ; i ++ ) p += a[i];
    t = gcd(p > 0 ? p : -p , q);
    printf("%lld/%lld\n" , p / t , q / t);
    return 0;
}

 

 

posted @ 2017-12-10 14:20  GXZlegend  阅读(504)  评论(0编辑  收藏  举报