题目大意:
给出一个序列,长度为N,均为正数。
找出一段连续的区间,此区间的平均值最大,长度必须大于F。
好像还是有点实际用途的,这个问题。
看完题之后,基本上就知道是做不出来的了。只想得到那种最简单的O(N^2)的解法,但是N = 100,000。这种解法必然超时。
在网上搜了两个解题报告,发现此题的解法相当牛逼!
两种解法是完全不同类型的。
二分法
我们可以比较容易得出答案的最大值和最小值,即为序列中最大元素和最小元素。
二分法的关键在于判断“一个可能的解跟正确答案相比是大了还是小了”。网上给的方法是:
如果要判断val这个解,那就让序列里所有元素的值都减去val。
然后试图寻找一段连续的区间,该区间的长度大于F,并且区间大于0。
可见,问题一下转化成统计数字的和,而不是数字的平均值,问题变得明朗了。
寻找这种区间的算法是一个很简单的动态规划,复杂度为O(N)。
用 f[a, b] 表示在区间 [a, b] 中,所有子区间的最大值。
那么 
当 b - a = F 时,f[a, b] 为序列中对应的和。
当 b - a > F 时,f[a, b] = max{ f[a, b - 1] + arr[b], f[b - f + 1, b] }
我们要求的是 f[0, N]。
因此,二分法的复杂度是 O(NlgN)。代码跑了接近300ms。

 /**//*
/**//*
 *    代码大量参考这份解题报告
 *    代码大量参考这份解题报告
 *    http://blog.sina.com.cn/s/blog_5c95cb070100dd47.html
 *    http://blog.sina.com.cn/s/blog_5c95cb070100dd47.html
 *    原作者代码写得很不错!赞一个!
 *    原作者代码写得很不错!赞一个!
 */
 */
 #include <stdio.h>
#include <stdio.h>

 #define MAX_N 100032
#define MAX_N 100032

 double S[MAX_N], A[MAX_N];
double S[MAX_N], A[MAX_N];
 int N, F;
int N, F;

 int check(double val)
int check(double val)


 {
{
 double cur, pre;
    double cur, pre;
 int i;
    int i;

 pre = S[F - 1] - val * (F - 1);
    pre = S[F - 1] - val * (F - 1);

 for (i = F; i <= N; i++)
    for (i = F; i <= N; i++)  {
{
 cur = S[i] - S[i - F] - val * F;
        cur = S[i] - S[i - F] - val * F;
 pre = pre + A[i] - val;
        pre = pre + A[i] - val;
 if (cur > pre)
        if (cur > pre)
 pre = cur;
            pre = cur;
 if (pre > -1e-6)
        if (pre > -1e-6)
 return 1;
            return 1;
 }
    }

 return 0;
    return 0;
 }
}

 int main()
int main()


 {
{
 int i;
    int i;
 double l, r, m;
    double l, r, m;

 freopen("e:\\test\\in.txt", "r", stdin);
    freopen("e:\\test\\in.txt", "r", stdin);

 scanf("%d%d", &N, &F);
    scanf("%d%d", &N, &F);
 l = 1e50;
    l = 1e50;
 r = 0;
    r = 0;
 A[0] = S[0] = 0;
    A[0] = S[0] = 0;

 for (i = 1; i <= N; i++)
    for (i = 1; i <= N; i++)  {
{
 scanf("%lf", &A[i]);
        scanf("%lf", &A[i]);
 if (A[i] > r)
        if (A[i] > r)
 r = A[i];
            r = A[i];
 if (A[i] < l)
        if (A[i] < l)
 l = A[i];
            l = A[i];
 S[i] = S[i - 1] + A[i];
        S[i] = S[i - 1] + A[i];
 }
    }


 while (r - l >= 1e-6)
    while (r - l >= 1e-6)  {
{
 m = (l + r) / 2;
        m = (l + r) / 2;
 if (check(m))
        if (check(m))
 l = m;
            l = m;
 else
        else
 r = m;
            r = m;
 }
    }

 printf("%d\n", (int)(r * 1000));
    printf("%d\n", (int)(r * 1000));

 return 0;
    return 0;
 }
}

这个方法不是真的求点的凸包,是用了求凸包时候的技巧。
首先把序列转化成一个图,一共有N个点,第 i 个点的坐标为 (i, S[i]),其中 S[i] 为序列的前 i 项和。
在图上,能观察到,点a点b之间的斜率就是区间[a, b]的平均值。
当 N = 6, F = 3 的时候,按照最简单的 O(N^2) 的做法,计算每两个点之间的斜率,计算的顺序为:
[1, 3]
[1, 4] [2, 4]
[1, 5] [2, 5] [3, 5]
[1, 6] [2, 6] [3, 6] [4, 6]
在算第6个点的时候,依次算了1,2,3,4跟点6的斜率。
为了避免不必要的计算,我们要没必要计算的点剔除。
用类似凸包的计算更新方法,在点1,2,3。。。中维护一条“下凸折线”。
这样,可以保证末尾的点跟折线中的点的斜率是先递增再递减的关系。
就能比较快的找出最大的斜率了。
这个算法的复杂度,网上的人说是O(N),但我觉得好像不是O(N)啊,也不知道是什么。
但是,绝对不能单单以复杂度来评价算法的啦。
代码跑了150ms左右。比2分的还是快一点。

 /**//*
/**//*
 *    思路参考此解题报告
 *    思路参考此解题报告
 *    http://hi.baidu.com/ultramanzhy/blog/item/a8cb4efa1ecf2e1aa9d31123.html
 *    http://hi.baidu.com/ultramanzhy/blog/item/a8cb4efa1ecf2e1aa9d31123.html
 *    解法牛逼!赞一个!
 *    解法牛逼!赞一个!
 */
 */
 #include <stdio.h>
#include <stdio.h>

 #define MAX_N 100032
#define MAX_N 100032

 int S[MAX_N], stack[MAX_N], N, F, sp;
int S[MAX_N], stack[MAX_N], N, F, sp;

 __inline int turn_right(int a, int b, int c)
__inline int turn_right(int a, int b, int c)


 {
{
 int x1, y1, x2, y2;
    int x1, y1, x2, y2;

 x1 = b - a;
    x1 = b - a;
 y1 = S[b] - S[a];
    y1 = S[b] - S[a];
 x2 = c - b;
    x2 = c - b;
 y2 = S[c] - S[b];
    y2 = S[c] - S[b];

 return x1*y2 - x2*y1 <= 0;
    return x1*y2 - x2*y1 <= 0;
 }
}

 __inline double calc_k(int a, int b)
__inline double calc_k(int a, int b)


 {
{
 return (double)(S[b] - S[a]) / (double)(b - a);
    return (double)(S[b] - S[a]) / (double)(b - a);
 }
}

 int main()
int main()


 {
{
 int i, j;
    int i, j;
 double max_val, val;
    double max_val, val;

 freopen("e:\\test\\in.txt", "r", stdin);
    freopen("e:\\test\\in.txt", "r", stdin);

 scanf("%d%d", &N, &F);
    scanf("%d%d", &N, &F);

 for (i = 1; i <= N; i++)
    for (i = 1; i <= N; i++)  {
{
 scanf("%d", &j);
        scanf("%d", &j);
 S[i] = S[i - 1] + j;
        S[i] = S[i - 1] + j;
 }
    }
 
    
 max_val = 0;
    max_val = 0;

 for (i = 0; i <= N - F; i++)
    for (i = 0; i <= N - F; i++)  {
{
 while (sp >= 2 && turn_right(stack[sp - 2], stack[sp - 1], i))
        while (sp >= 2 && turn_right(stack[sp - 2], stack[sp - 1], i))
 sp--;
            sp--;
 stack[sp++] = i;
        stack[sp++] = i;
 for (j = sp;
        for (j = sp; 
 j >= 2 && turn_right(stack[j - 2], stack[j - 1], i + F);
             j >= 2 && turn_right(stack[j - 2], stack[j - 1], i + F);
 j--
             j--
 );
             );
 val = calc_k(stack[j - 1], i + F);
        val = calc_k(stack[j - 1], i + F);
 if (val > max_val)
        if (val > max_val)
 max_val = val;
            max_val = val;
 }
    }
 printf("%d\n", (int)(max_val * 1000));
    printf("%d\n", (int)(max_val * 1000));

 return 0;
    return 0;
 }
}
