解题思路:
这相当于一个非递减数列,i是x坐标,sum[i]是y坐标,在这个图上找y-x>=L的斜率最大的两个点。用一个队列保存当前可能用到的起点,设i从L递增到N,我们每次在队列中加入i-L作为起点,设rear为当前队列中最后一个元素。如果发现rear和i-L这两个点的斜率小于等于rear-1和rear这两个点的斜率,也就是rear-1,rear,i-L三个点一次连成的曲线不是下凸的,就可以把rear这个点删掉了。这是因为后面再出现的点怎么都不可能和rear这个点构成最大斜率(可以自己画个图看一下)。从后删到不能删后,把i-L加到最后。不光可以从后删掉一些没用的点,还可以从前删掉一些。如果front那个点和i点的斜率小于front+1和i的斜率,那么front这个点可以删掉,因为y是单调不减的,后面再出现的点如果可能更新最大斜率的话,明显和front+1构成的斜率更优。这样又从前删掉了一些点。删完后i和当前的front就是i点作为end的最优答案。
总之,队列中的点都是当前有可能作为起点的,是一条下凸曲线。这样就比遍历一遍省时间,省去了一些没用的点。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
#include<algorithm>
using namespace std;
const int maxn=100005;
int len,l;
char s[maxn];
int sum[maxn],q[maxn];
double k(int x,int y)
{
return (sum[y]-sum[x])*1.0/(y-x);
}
int main()
{
//freopen("in.txt","r",stdin);
int t;
scanf("%d",&t);
while(t--)
{
scanf("%d%d%s",&len,&l,s);
int n=strlen(s);
sum[0]=0;
for(int i=1;i<=n;i++)sum[i]=sum[i-1]+s[i-1]-'0';
int front=0,rear=-1;
double ans=0;
int anslen=0,x=0,y=l;
for(int i=l;i<=n;i++)
{
int tmp=i-l;
while(front<rear&&k(q[rear],tmp)<=k(q[rear-1],q[rear]))rear--;
q[++rear]=tmp;
while(front<rear&&k(q[front],i)<=k(q[front+1],i))front++;
double m=k(q[front],i);
int tmplen=i-q[front]+1;
if(m==ans&&tmplen<anslen||m>ans)
{
ans=m;
anslen=tmplen;
x=q[front],y=i;
}
}
printf("%d %d\n",x+1,y);
}
return 0;
}