HDU 2993 MAX Average Problem(斜率优化入门+单调队列)

来源:互联网 发布:仓库,财务办公软件 编辑:程序博客网 时间:2024/06/06 15:40
 


单调队列说明:

单调队列,顾名思义就是具有单调性的队列,一般的队列只能从队尾入队、队首出队;为了保持单调队列的单调性,单调队列除具有这两种性质外,还可以从队尾出队。
以单增的单调队列为例,当元素t要入队时,先要从队尾依次弹出所有>=t的元素,再将t加在队尾。
举个例子,如果序列:1 3 -1 -3 10要构成单调队列,
先将元素“1”放入队列中,以初始化队列,
接着元素“3”要入队,队尾元素“1”比“3”小,因此“3”可以直接入队,队列变为1 3,
接着“-1”要入队,从队尾依次弹出元素“3”“1”后将“-1”入队,队列变为-1,
同理“-3”入队后,队列变为-3,
“10”入队后,队列变为-3 10

 

斜率优化

可以参考周源写的《浅谈数形结合思想在信息学竞赛中的应用》

 

题目大意:读入一列正数(包含N个数),求其中长度大于等于K的子串中所有数的平均值的最大值(K<=N<=10^5)。(最大平均值问题)

分析:1、读入一列正数,a1,a2,...,aN,以及一个数K。定义ave(i,j)=(ai+...+aj)/j-i+1,i<=j。求Max{ave(a,b),1<=a,b<=N,且a<=b+K-1},求一段长度大于等于K且平均值最大的子串。

           2、如果单纯的枚举,每次枚举一对满足条件的(a,b),即a<=b-K+1,检查ave(a,b),并更新当前最大值。但是这题N很大,N^2的枚举算法显然不能用。

           3、巧妙的使用斜率优化:

               a)目标图形化:从第a到第b个数的平均值实际上就可以表示为(Sb-Sa)/(b-a),其中Si表示前i项和。那么如果在坐标系中用(i,Si)表示一个点,

                                       则平均值就是两点确定的直线的斜率。这样化为几何图,就只需要找到最大的且符合题意的斜率就可以了。

               b)构造下凸折线:类似于凸包。。这里寻找出现的可能的上凸点,将之去掉,就可以减少检查的范围。

                                           关于上凸点:

                                                  如果三个数i,j,k,如果KK(i,j)>KK(j,k),其中KK表示两点所连直线的斜率,那么j点就是凸点。

                                                  这些上凸点不可能对最优结果又贡献。

               c)维护下凸曲线

               d)利用下凸曲线的单调性

          4、Pi的检查集合为Gi={Pj,0<=j<=i-K},规定i<j。

 

代码:

#include<cstdio>
#include<iostream>
#include<cstring>#define findmax(a,b) (a)>(b)?(a):(b)using namespace std;int sum[100005];int que[100005];int a[100005];int getin()       //getin()函数优化输入,如果有scanf读入,不管用c++还是G++提交都会CE{    char c;    int dd;    while(c=getchar(),c<'0'||c>'9');    dd=c-'0';    while(c=getchar(),c>='0'&&c<='9')        dd=dd*10+c-'0';    return dd;}double findk(int i,int j)               //求斜率{    double kk;    kk=1.0*(sum[i]-sum[j])/(i-j);    return kk;}int main(){    int n,k,i,j,head,rear;    double ans;    while(scanf("%d%d",&n,&k)!=EOF)    {        sum[0]=0;        for(i=1;i<=n;i++)        {            sum[i]=sum[i-1]+getin();        }        que[0]=0;            //队列初始化        head=rear=ans=0;         //ans存每次更新的最大平均值即最大斜率,head和rear是单调队列的头指针和尾指针        for(i=k;i<=n;i++)        //从第K个开始检查,因为至少要相隔K个数        {            j=i-k+1;            while(head<rear&&findk(que[head],i)<=findk(que[head+1],i))        //维护单调队列,保证单增队列的头元素必是最大的                head++;            ans=findmax(ans,findk(i,que[head]));                               //更新ans的值            while(head<rear&&findk(j,que[rear])<=findk(que[rear],que[rear-1]))    //每次遇到上凸点就退队                rear--;            que[++rear]=j;         //把与检查的点相隔k的那个数加入队列        }        printf("%.2lf\n",ans);    }    return 0;}


 

 

 

测试样例的模拟过程:

(i,s[i])

 

(0,0)(1,6)(2,10)(3,12)(4,22)(5,25)(6,33)(7,38)(8,47)(9,51)(10,32)

 

首先,要从第6个点检查起,为什么呢,因为至少要相隔6个单位

 

检查(6,33),单调队列中只有一个(0,0),因为其他不符合要求

(0,0),(6,33),斜率为33/6=5.5

 

检查(7,38)(1,6)进单调队列,单调队列中有(0,0),(1,6),指针在(0,0)

计算(0,0),(7,38),斜率为5.43

计算(1,6)(7,38),斜率为5.33

指针不动,最大斜率还是5.5

 

检查(8,47)(2,10)进单调队列,单调队列中有(0,0),(1,6),(2,10),计算(0,0)(1,6)的斜率为6(1,6)(2,10)的斜率为4,因此(1,6)弹出单调队列,单调队列中有(0,0),(2,10)                       //这里是(1,6)先退队,再把(2,10)加入队列

计算(0,0),(8,47),斜率为5.875

计算(2,10), (8,47),斜率为6.17

指针指向(2,10),最大斜率更新为6.17

 

……

后面自行补充