题目链接:http://poj.org/problem?id=2018
大致题意:在N (1 <= N <= 100,000)块田中,选取一个长度不短于 F (1 <= F <= N)的连续区间,使得这个区间的平均值最大。
Time Limit: 1000MS Memory Limit: 30000K
样例:
Sample Input
10 6 6 4 2 10 3 8 5 9 4 1
Sample Output
6500
思路:看这个题目我们很容易想到O(n^2)的DP状态转移方程。但是这个N的范围是10^5,O(n^2)严重超时。此题的一种解法是斜率优化DP(求平均值最大是维护凸包的过程,故存在斜率优化策略)。另外一种解法是01分数规划。
这里讲一下第二种的思路,假定我们已经知道最大的平均值(平均值在这个数组的最大数和最小数之间),所有的数都减去这个平均值,那么我们就能求出一个最大连续子段的和跟0做比较,如果字段和大于0,说明我们假设的这个最大平均值偏小了,如果小于0则偏大。我们可以通过二分平均值,找到答案。这样我们就把原来的求平均值的问题转化为求长度限制的连续子段和问题了。时间复杂度是O(lgN),N跟需要的精度有关。分数规划思路就是假设答案,推出答案的组成。
长度不小于f的最大连续字段和比最大连续字段和多加了一个长度限制的条件,其实我们只要保证这个序列长度至少是f就可以了。
状态转移方程是:dp[i]=max(num[i],dp[i-f]+num[i]),dp[i]是指去掉i前面f-1个数的最大连续子段和,dp[i]+num[i-f]+num[i-f+1]+...+num[i-1]就是保证长度至少为f的以num[i]为结尾的最大连续字段和。时间复杂度是O(N)。
总的时间复杂度是O(N*lgN)。
1 #include<cstdio> 2 const double inf=1e-6; 3 const int MAXNUM=100005; 4 double num[MAXNUM]; 5 double temp[MAXNUM]; 6 double dp[MAXNUM]; 7 double max(double a,double b){ 8 if(a>b)return a; 9 return b; 10 } 11 double min(double a,double b){ 12 if(a<b)return a; 13 return b; 14 } 15 int main(){ 16 int n,f,i,j; 17 double left,right,mid; 18 while(~scanf("%d%d",&n,&f)){ 19 left=2000,right=0; 20 for(i=0;i<n;i++){ 21 scanf("%lf",&num[i]); 22 left=min(num[i],left); 23 right=max(right,num[i]); 24 } 25 while(right-left>inf){ 26 mid=(right+left)/2; 27 for(i=0;i<n;i++)temp[i]=num[i]-mid; 28 dp[0]=temp[0]; 29 for(i=1;i<f;i++)dp[i]=max(temp[i],dp[i-1]+temp[i]); 30 double ans=0,sum=0; 31 for(i=1;i<f;i++)sum+=temp[i]; 32 for(i=0;i<f;i++)ans+=temp[i]; 33 for(i=f;i<n;i++){ 34 dp[i]=max(temp[i],dp[i-f]+temp[i]); 35 ans=max(ans,sum+dp[i]); 36 sum=sum-temp[i-f+1]+temp[i]; 37 } 38 if(ans>0)left=mid; 39 else right=mid; 40 } 41 int ans=1000*right; 42 printf("%d\n",ans); 43 } 44 return 0; 45 }