题目链接:https://uva.onlinejudge.org/index.php?option=com_onlinejudge&Itemid=8&page=show_problem&problem=4197
题目大意:在01序列中找到一个长度不小于L的序列,使得该序列的平均值最大,输出区间端点,并保证左端点最靠左。
求平均值也就是求(a[l]+a[l+1]+···+a[r])/(r-l+1)
设前缀和为s,则等价于求max((s[r]-s[l-1])/(r-(l-1)))/,即求max((s[r]-s[l])/(r-l))
即(i,s[i])两点的最大斜率,维护凸包(下凸),如图
最优点即为当前点与凸包的切点
对于不满足下凸的点一定不是最优点,所以只需每次维护单调性是图形为下凸
并且可知,当前不为最优解的前端的点,在后面也不可能是最优解,所以同样从队头删除
再用队头更新。
总结一下,单调队列的步骤:
1.删除队尾维护单调性
2.插入队尾
3.删除队头不优的点
4.用队头更新
PS:把有的部分写成函数可以让代码更简洁一些。。。以及,初值要注意啊
代码如下:
#include<cstdio>
using namespace std;
int T;
int n,L;
int s[100050];
int q[100050];
int l,r;
int ansl,ansr;
char op;
int now;
int get(int a,int b,int c,int d)
{
return (s[a]-s[b])*(c-d)-(s[c]-s[d])*(a-b);
}
int main()
{
scanf("%d",&T);
while (T--)
{
scanf("%d%d",&n,&L);
getchar();
s[0]=0;
for (int i=1;i<=n;i++)
{
op=getchar();
s[i]=op-'0'+s[i-1];
}
l=1;
r=0;
ansl=0;
ansr=L;
for (int i=L;i<=n;i++)
{
now=i-L;
while (l<r && get(q[r-1],q[r],q[r],now)>0)
{
r--;
}
r++;
q[r]=now;
while (l<r && get(q[l+1],i,q[l],i)>=0)
{
l++;
}
if (get(q[l],i,ansl,ansr)>0 || (get(q[l],i,ansl,ansr)==0 && ansr-ansl>i-q[l]))
{
ansl=q[l];
ansr=i;
}
}
ansl++;
printf("%d %d\n",ansl,ansr);
}
return 0;
}
PS:把有的部分写成函数可以让代码更简洁一些。。。