题意: 给出一个长度为n的01序列, 要你求出一段至少长度为L的连续子序列, 该子序列的数字的平均值 最大, 多解尽量保证长度小, 在保证起点编号尽量小, 求出起点和终点编号。
/**==========================================
* This is a solution for ACM/ICPC problem
*
* @source: UVA - 1451 Average
* @type: 数形结合
* @author: wust_ysk
* @blog: http://blog.csdn.net/yskyskyer123
* @email: 2530094312@qq.com
*===========================================*/
#include<cstdio>
#include<string>
#include<cstring>
#include<iostream>
#include<cmath>
#include<algorithm>
#include<set>
using namespace std;
typedef long long ll;
const int INF =0x3f3f3f3f;
const int maxn= 100000 ;
int n,L;
char s[maxn+3];//存储读取的字符串
int sum[maxn+3];//sum[x]表示[1,x]元素之和
int q[maxn+4];//队列
void pre()
{
sum[0]=0;
for(int i=1;i<=n;i++)
{
sum[i]=sum[i-1]+(s[i]=='1'?1:0);
}
}
int deltaAverage(int x1,int x2,int x3,int x4)//[x1,x2],[x3,x4]段的平均值之差,
//也可理解为线段[ (x1-1,sum[x1-1] ),(x2,sum[x2])]和线段[ (x3-1,sum[x3-1] ),(x4,sum[x4])]的斜率之差
{
return (sum[x2]-sum[x1-1])*(x4-x3+1) -(sum[x4]-sum[x3-1])*(x2-x1+1);
}
void work()
{
int ansL=1,ansR=n;//答案区间[ansL,ansR];
int rear=1,front=1;
for(int i=L;i<=n;i++)
{
while(front+1<rear&&deltaAverage(q[rear-2],i-L,q[rear-1],i-L)>=0 ) rear--;//易错
//上凸点一定不会是最优点,所以啊sum[上凸点+1]不可能作为ansL,删除之。
//因为这里[ansL,ansR]的斜率其实是ansL-1,ansR这条直线。
//这个while循环的i-L不可以写成q[rear-1]
//因为要删除一些不可能成为ansL的点,所以把如果i-L放入,所有不满足条件的点都删除。
q[rear++]=i-L+1;
while(front+1<rear&&deltaAverage(q[front],i,q[front+1],i)<=0 ) front++;
//画图可知,切点不会往左移,切点不递减。
int f=deltaAverage( q[front],i ,ansL , ansR );
if( f>0 || !f&& i-q[front]<ansR-ansL )//更新答案
{
ansL=q[front];
ansR=i;
}
}
printf("%d %d\n",ansL,ansR);
}
/*
其实枚举的方向起到了关键的作用,这是模仿代码库中的写法写的,对于每个x作为ansR,找到最优的ansL。
而不是对于每个x作为ansL,找到最优的ansR。
这个题目的障碍是时间复杂度太高,解题的关键在于:对于每个R,要深入分析[1,R-L+1]区间内点的关系。
一定是会剔除某些点的。
而且对于每个x是当作ansR还是当作ansL是很重要的。
*/
int main()
{
int T;
scanf("%d",&T);
while(T--)
{
scanf("%d%d",&n,&L);
scanf("%s",s+1);
pre();
work();
}
return 0;
}