把博客园图标替换成自己的图标
把博客园图标替换成自己的图标end

【BZOJ3316】JC loves Mkk(分数规划+单调队列)

点此看题面

大致题意: 给定一个圆环,你可以选择连续\(L\sim R\)个数(个数必须为偶数),求最大的平均值。

分数规划

这种平均值的问题一看就是分数规划

考虑我们二分一个答案\(x\),设我们选择的数是\(a_{1\sim t}\)则需要满足:

\[\frac{\sum_{i=1}^ta_i}t\ge x\Leftrightarrow\sum_{i=1}^t(a_i-x)\ge 0 \]

也就是说,给所有数减去\(x\)之后,就是要求是否存在一个长度为偶数且在\(L \sim R\)范围内的区间使得这段区间内数的总和大于等于\(0\)

单调队列

感觉没什么好说的吧。

单调队列维护前缀和的最小值即可。

唯一要注意的就是因为长度必须是偶数,要开两个单调队列。

关于答案

我们发现,二分出的答案是小数,而题目要求分数。

实际上,我们只要记录下得出答案时的长度,然后以答案乘长度为分子,长度为分母,约分一下就可以了。

代码

#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 200000
#define DB long double
#define eps 1e-8
using namespace std;
int n,k,Mn,Mx,a[N+5],q[2][N+5];DB s[N+5];
I long long gcd(Con long long& x,Con long long& y) {return y?gcd(y,x%y):x;}
I bool Check(Con DB& x)//验证答案
{
	RI i,op,H[2]={1,1},T[2]={0,0};
	for(i=1;i^Mn;++i) s[i]=s[i-1]+a[i]-x;for(;i<=n;++i)//计算前部分的前缀和
	{
		op=i&1,H[op]<=T[op]&&q[op][H[op]]==i-Mx&&++H[op];//弹出不合法元素
		W(H[op]<=T[op]&&s[q[op][T[op]]]>=s[i-Mn]) --T[op];q[op][++T[op]]=i-Mn;//加入新元素
		if((s[i]=s[i-1]+a[i]-x)-s[q[op][H[op]]]>=-eps) return k=i-q[op][H[op]],1;//如果符合条件直接return 1
	}return 0;
}
int main()
{
	RI i;scanf("%d%d%d",&n,&Mn,&Mx),Mn&1&&++Mn,Mx&1&&--Mx;
	for(i=1;i<=n;++i) scanf("%d",a+i),a[n+i]=a[i];n<<=1;//圆环,所以复制一遍
	DB l=0,r=1e9,mid;W(r-l>eps) Check(mid=(l+r)/2)?l=mid:r=mid;//分数规划
	Check(l);long long s=l*k+0.5,g=gcd(s,k);
	return g^k?printf("%lld/%d",s/g,k/g):printf("%d",s/g),0;//输出分数
}
posted @ 2020-06-04 19:44  TheLostWeak  阅读(144)  评论(0编辑  收藏  举报