【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;
}
浙公网安备 33010602011771号