题目描述
给出一个长度为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])(0le jle i-k)$ 是否大于等于0。存在某一个大于等于0则有解,否则无解。
但是答案询问的是分数形式怎么办?只需要每次记录答案的选择方式,最后根据这个方式再计算一遍即可。
时间复杂度 $O(nlog 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 " , p / t , q / t); return 0; }