HDU-2993--MAX Average Problem详解

此题是关于DP的优化问题,具体解题思路贴在后面
此题大意:
读入一列正数N,a1, a2, …, aN,以及一个数F。定义ave(i,j)=ai到aj的平均值,j-i+1>=k,
求一个最大的ave(i,j)
 
首先我先把代码贴上来:
 
View Code
 1 //====================================================================
 2 //Name       :hdu 2993 MAX Average Problem
 3 //Author     :hxf
 4 //copyright  :http://blog.sina.com.cn/u/2904525152
 5 //Description:动态规划DP斜率优化
 6 //Data       :2012.7.17
 7 //========================================================================
 8 #include <iostream>
 9 #include "stdio.h"
10 #include<algorithm>
11 #define LEN 100001
12 using namespace std;
13 int temp[LEN],head,tail;//用来控制判断图上点的取舍
14 /////
15 int GetInt(){/////这里是读入外挂(没这个这题貌似AC不掉)
16     char ch=getchar();
17     while(ch<'0'||ch>'9')ch=getchar();
18     int num=0;
19     while(ch>='0'&&ch<='9'){
20         num=num*10+ch-'0';
21         ch=getchar();
22     }
23     return num;
24 }
25 ////
26 void solve(int n,int k)
27 {
28     double sum[LEN];//用来保存部分和
29     memset(sum,0,sizeof(sum));
30     sum[0]=0;
31     for(int i=1;i<=n;i++)
32     {
33         int x=0;
34         //scanf("%d",&x);
35         x=GetInt();
36         sum[i]=sum[i-1]+x;
37     }
38     //输入完成
39     memset(temp,0,sizeof(temp));
40     temp[0]=0;head=0;tail=0;
41     double max=0;
42 //具体实现优化
43     for(int i=k;i<=n;i++)
44     {
45         int now=i-k;
46         while(head<tail)
47         {
48             double k1=(sum[temp[tail]]-sum[temp[tail-1]])/(temp[tail]-temp[tail-1]);
49             double k2=(sum[now]-sum[temp[tail]])/(now-temp[tail]);
50             if(k1>=k2)
51                 tail--;
52             else
53                 break;//去掉向上凸出来的点
54         }
55         //
56         tail++;
57         temp[tail]=now;
58 ///
59         while(head<tail)
60         {
61             double k1=(sum[temp[head]]-sum[i])/(temp[head]-i);
62             double k2=(sum[temp[head+1]]-sum[i])/(temp[head+1]-i);
63             if(k1<=k2)
64                 head++;//下凸线中去掉斜率较小的点(从左往右)
65             else
66                 break;
67         }
68         double t=(sum[temp[head]]-sum[i])/(temp[head]-i);
69         if(t>max)
70             max=t;
71     }
72     printf("%.2lf\n",max);
73 }
74 int main()
75 {
76     int n=0,k=0;
77     while(scanf("%d%d",&n,&k)!=EOF)
78     {
79         solve(n,k);
80     }
81 return 0;
82 }

现在来分析解题思路:
   首先一定会设序列ai的部分和:Si=a1+a2+…+ai,,特别的定义S0=0。
   这样可以很简洁的表示出目标函数ave(i,j)=(Sj-S(i-1))/(j-(i-1))!
   如果将S函数绘在平面直角坐标系内,这就是过点 Sj和点Si-1直线的斜率
于是问题转化为:平面上已知N+1 个点,Pi(i, Si),0≤i≤N,求横向距离大
于等于F的任意两点连线的最大斜率。
 
构造下凸折线
   有序化一下,规定对i<j,只检查Pj向Pi的连线,对Pi不检查与Pj的连线。
也就是说对任意一点,仅检查该点与在其前方的点的斜率。于是我们定义点Pi
的检查集合为
Gi = {Pj, 0≤j≤i-F}
   特别的,当i<F时,Gi为空集。
   其明确的物理意义为:在平方级算法中,若要检查ave(a, b),那么一定有Pa∈Gb;
因此平方级的算法也可以这样描述,首先依次枚举Pb点,再枚举Pa∈Gb,同时检查k(PaPb)。//k为斜率
 
   若将Pi和Gi同时列出,则不妨称Pi为检查点,Gi中的元素都是Pi的被检查点。
   当我们考察一个点Pt时,朴素的平方级算法依次选取Gt中的每一个被检查点p,
考察直线pPt的斜率。但仔细观察,若集合内存在三个点Pi, Pj, Pk,且i<j<k,三个点形成如下图
所示的的关系, 即Pj点在直线PiPk的上凸部分:k(Pi, Pj)>k(Pj,Pk),就很容易可以证明Pj点是多余的


    若k(Pt, Pj) > k(Pt, Pi),那么可以看出,Pt点一定要在直线PiPj的上方,即阴
影所示的1号区域。同理若k(Pt, Pj) > k(Pt, Pk),那么Pt点一定要在直线PjPk的下
方,即阴影所示的2号区域。
 
     综合上述两种情况,若PtPj的斜率同时大于PtPi和PtPk的,Pt点一定要落在两阴影的重叠部分,
但这部分显然不满足开始时t>j 的假设。于是,Pt落在任何一个合法的位置时,PtPj的斜率要么小于PtPi,
要么小于PtPk,即不可能成为最大值,因此Pj点多余,完全可以从检查集合中删去。 这个结论告诉我们,
任何一个点Pt的检查集合中,不可能存在一个对最优结果有贡献的上凸点,因此我们可以删去每一个上凸点,
剩下的则是一个下凸折线。最后需要在这个下凸折线上找一点与Pt 点构成的直线斜率最大——显然这条直
线是在与折线相切时斜率最大,如图所示。


维护下凸折线
      这一小节中,我们的目标是:用尽可能少的时间得到每一个检查点的下凸折线。
     算法首先从PF开始执行:它是检查集合非空的最左边的一个点,集合内仅有一个元素P0,
而这显然满足下凸折线的要求,接着向右不停的检查新的点:PF+1,PF+2, …, PN。
 
     检查的过程中,维护这个下凸折线:每检查一个新的点Pt,就可以向折线最右端加入一个新的点Pt-F,
同时新点的加入可能会导致折线右端的一些点变成上凸点,我们用一个类似于构造凸包的过程依次删去这些上凸点,
从而保证折线的下凸性。由于每个点仅被加入和删除一次,所以每次维护下凸折线的平摊复杂度为O(1),
即我们用O(N)的时间得到了每个检查集合的下凸折线。
 
最后的优化:利用图形的单调性
     最后一个问题就是如何求过Pt点,且与折线相切的直线了。一种直接的方法就是二分,每次查找的复杂度是O(log2N)。
但是从图形的性质上很容易得到另一种更简便更迅速的方法:
由于折线上过每一个点切线的斜率都是一定的,而且 根据下凸函数斜率的单调性,如果在检查点Pt 时找到了折线上的已知一个切点 A,
那么A以前的所有点都可以删除了:过这些点的切线斜率一定小于已知最优 解,不会做出更大的贡献了。
     于是另外保留一个指针不回溯的向后移动以寻找切线斜率即可,平摊复杂度为为O(1)。
      至此,此题算法时空复杂度均为O(N),得到了圆满的解决。
 
呼呼,终于贴完了
本人菜鸟一个

第一次发算法的贴,实在经验不足,若是写的不好,大家多多指教哈~

特别感谢:
《浅谈数形结合思想在信息学竞赛中的应用 》---周源
提供了许多关于数形结合解题的思路。
                                                                            幻o飞
                                                                         2012.7.17与凌晨2点

转载于:https://www.cnblogs.com/Free-rein/archive/2012/08/09/2630190.html

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值