poj 2018 斜率优化(最大平均值问题)

题意:给出一个序列,长度为n(n<=100000)均为正数。找出一段连续的区间,使得区间的平均值最大,满足区间的长度必须大于等于m。

思路:就这道题来说采用斜率优化和dp没什么关系了,主要就是数形结合,相当于斜率对于枚举的优化。参考了周源的论文,文章讲述的非常清晰。

首先i~j区间的平均值转换为sum(j)-sum(i-1)/j-(i-1),即转换为两点的斜率,于是问题转化为:平面上已知N+1个点,Pi(i, Si),0≤i≤N,求横向距离大于等于F的任意两点连线的最大斜率。接下来规定枚举方向,即考虑点i时只需考虑0<=j<=i-m的那些点(j构成考虑集合G)。接下里证明上凸点无用(具体见论文)。那么维护一个下凹的点集即可(思路类似于求凸包)。接着找最大斜率时再次用了这个思路,维护一个不减的下标即可。整个的均摊复杂度为O(n),非常精彩。

#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <cmath>
#include <algorithm>
#include <queue>
using namespace std;
#define INF 0x3fffffff
#define clr(s,t) memset(s,t,sizeof(s))
#define N 100005
int n,m;
int s[N],stack[N],top;
long long test(int a,int b,int c){
    return (long long)(b-a)*(s[c]-s[a])-(long long)(c-a)*(s[b]-s[a]);
}
int main(){
    int i,now,res;
    scanf("%d %d",&n,&m);
    s[0] = now = res = 0;
    top = -1;
    for(i = 1;i<=n;i++){
        scanf("%d",&s[i]);
        s[i] += s[i-1];
    }
    for(i = m;i<=n;i++){
        while(top > 0){//维护下凹点集
            if(test(i-m,stack[top],stack[top-1])<0)
                break;
            top--;
        }
        stack[++top] = i-m;
        while(now<top){//找斜率最大点
            if(test(i,stack[now],stack[now+1])<0)
                break;
            now++;
        }
        now = min(now,top);//忘了写wa了一次,因为经过出栈之后top可能小于now,那么直接取top即可
        res = max(res,(int)((s[i]-s[stack[now]])*1./(i-stack[now])*1000));
    }
    printf("%d\n",res);
    return 0;
}


你可能感兴趣的:(poj 2018 斜率优化(最大平均值问题))