1.求一个子段,它的和最大。(动态规划)
ll dp[maxn], a[maxn];
int main()
{
cin >> n;
for(int i = 1; i <= n; i++)
{
cin >> a[i];
dp[i] = a[i];
}
for(int i = 1; i <= n; i++)
{
dp[i] = max(dp[i], dp[i - 1] + a[i]);
}
ll ans = 0;
for(int i = 1; i <= n; i++)ans = max(ans, dp[i]);
cout<<ans<<endl;
return 0;
}
2.求一个子段,他的和最大,子段的长度不小于L.
求一个子段它的和最大且长度不小与L。子段和可以转化为前缀和相减的形式,即设sumi表示ai~aj的和,则:技术分享图片
仔细观察上面的式子容易发现,随着i的增长,j的取值范围0~i-L每次只会增大1。换言之,每次只会有一个新的取值进入min{sumj}的候选集合,所以我们没有必要每次循环枚举j,只需要用一个变量记录当前的最小值,每次与新的取值sumi-L取min就可以了。
于是我们只需要看一下最大子段和是不是非负数就可以确定二分上下界的变化范围了。
(没看懂没事,第一遍我也没看懂,仔细看并对照代码,f**k真的简单巧妙!)
double min1=1e10;
double ans=-1e10;
for(int i=l;i<n;i++){
min1=min(min1,sum[i-l]);
ans=max(ans,sum[i]-min1);
}
3.给定一个正整数数列A,求一个平均数最大的、长度不小于L的子段。
思路:
二分平均数为mid,把数组数组都减去mid,若最大和>0,就是平均数取小了。
这就涉及到均值比较的技巧:平均值大小的比较,可以转换为每对对应元素差值之和与0的比较。
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
double a[100005],b[100005],sum[100005];
int main(){
int n,L;
cin>>n>>L;
for(int i=0;i<n;i++) scanf("%lf",&a[i]);
double eps=1e-5;
double l=-1e6,r=1e6;
while(r-l>eps){
double mid=(r+l)/2;
for(int i=0;i<n;i++) b[i]=a[i]-mid;
for(int i=0;i<n;i++) sum[i]=sum[i-1]+b[i];
double ans=-1e10,minval=1e10;
for(int i=L-1;i<n;i++){
minval=min(minval,sum[i-L]);
ans=max(ans,sum[i]-minval);
}
if(ans>0) l=mid; else r=mid;
}
cout<<int(r*1000)<<endl;
return 0;
}