MAX Average Problem
Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others) Total Submission(s): 4416 Accepted Submission(s): 1106
这个也是论文上的例题,给你一个正数序列,注意全是正数,否则就不满足和的单调性了,关于这个结论某些部分就不能在运用了,我先说一下前提条件,我就是前提条件没搞请,才弄的模模糊糊的,弄清楚前提条件后就会发现其实不是很难的。
前提条件:
1.所有的数均是(保证了他们的和是单调递增的)
2.求的是连续的序列,且序列的长度大于等于k
3.求的是序列的最大平均值,也就是(i, sum[i])值中斜率最大的那个值
解体思路:
现在我只是干讲,如果感觉不形象的话,可以参考那篇著名的论文。
由于我们是要求斜率最大值,如果枚举的话,我们肯定是把当前的点与它前面的所有的点求斜率,取最大值。
现在我们想降低复杂度,我们可以从两个方面入手,
1,去掉每个位置前面的那些在后来的枚举中不可能产生最优解的点
2,快速的去定位最优解
首先讲第一个:
1.如何减少不必要的枚举呢?
我们假设存在三个点,i,j, k三个点构成的是上凸包,现在我们沿着j,k作延长线,如何后来的点在延长线的下方,那么该后来的点是不能构成最优解的,因为当前延长线的解已经比后来的点构成的所有的点优或则说与i构成的直线的斜率一定比与j构成的斜率大,如果后来的点在延长线的上方,那么你会发现与k构成的斜率肯定比与j构成的斜率大,那么问题就出现了,j不可能在后来的更新中形成最优解,那么就可以将其删除,这样在后来的枚举中少了它了。很神奇的证明,不过的确特别的有用。
2.如何更快的去定位最优解呢?
一般的做法就是二分,不过这里的做法很绝,把原来O(NlgN)的做法优化到了,O(N)
还是把不可能构成最优解的删掉,(注意sum[i]是单调递增的),如果当前构不成最优解,那么在后来的点着中是不可能构成最优解的,因此可以删掉,到底为什么,还是自己看自己想。这里维持最小长度为k还是很巧秒的,不过HDU交C++可过,交G++TLE,据说只要有C++那么它的服务器系统肯定是windows,那么交G++调用MingW肯定会慢的。
1 #include<iostream> 2 #include<cstdio> 3 #include<cstring> 4 5 using namespace std; 6 7 const int maxn=100010; 8 9 double sum[maxn]; 10 int q[maxn]; 11 12 double cross(int i,int j,int k){ 13 double tmp; 14 tmp=(k-i)*(sum[j]-sum[i])-(j-i)*(sum[k]-sum[i]); 15 return tmp; 16 } 17 18 double max(double a,double b){ 19 return a>b?a:b; 20 } 21 22 int Input(){ 23 char c; 24 int ans; 25 while(c=getchar(),c<'0' || c>'9') ; 26 ans=c-'0'; 27 while(c=getchar(),c>='0' && c<='9') 28 ans=ans*10+c-'0'; 29 return ans; 30 } 31 32 double f(int i,int k){ 33 i=q[i]; 34 return (sum[k]-sum[i])/(k-i); 35 } 36 37 int main(){ 38 39 //freopen("input.txt","r",stdin); 40 41 int n,k; 42 while(~scanf("%d%d",&n,&k)){ 43 sum[0]=0; 44 int i,j; 45 for(i=1;i<=n;i++) 46 sum[i]=sum[i-1]+Input(); 47 int head=0,rear=0; 48 double ans=0; 49 for(i=k;i<=n;i++){ 50 j=i-k; 51 while(head<rear && cross(q[rear-1],q[rear],j)>=0) 52 rear--; 53 q[++rear]=j; 54 while(head<rear && f(head+1,i)>=f(head,i)) 55 head++; 56 ans=max(ans,f(head,i)); 57 } 58 printf("%.2lf\n",ans); 59 } 60 return 0; 61 }