题意:给出一个序列,长度为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; }