洛谷P1115最大字段和
这是该类问题的初级模式,即给与一个数列,求连续的一段元素的和最大。
我们可以想象,当某一子段 s 的和为正数时,当 s 加下一个数 a 时,对 a 来说,s可看成一个整体,那么 s 可以使由 s和 a组成的新子段变大,即s是有可能在最大子段中的,并且s是最大子段的头部。但是如果 s<0,如果 s 在最大子段中,如果将 s 去掉,那么最大子段就会变得更大,那么就矛盾了,所以 s 不存在于最大子段中。
所以我们只需要从第一个数开始扫一遍,如果s>0,那么就将a加进去,形成新的 s ,更新一下可能的最大子段和;否则,将 s 归零,然后再加上 a ,重复之前的更新操作(相当于舍弃了之前的 s,在新的情况下找可能的最大子段和)。
代码如下:
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<vector>
#include<cstring>
using namespace std;
int num[200005];
int main()
{
int N;
scanf("%d",&N);
for(int i=1;i<=N;i++)scanf("%d",&num[i]);
int ans=-100000000,s=0;
for(int j=1;j<=N;j++)
{
if(s>=0)
{
s+=num[j];
ans=max(ans,s);
}
else
{
s=0;
s+=num[j];
ans=max(ans,s);
}
}
cout<<ans;
}
POJ1050
这道题是最大子段和的一个升级版,这次要从一个大矩形中找一个子矩形,求子矩形的最大和。
从形式上和上一道题是差不多的,但是矩形最大的问题在于形状和区域范围的不确定,所以较难求解。为了转化模型,我们可以考虑对于一个矩形从第 i 行到第 j 行来说,其实可以等效于,把 i 到 j 之间的同一列的所有数的值相加,形成一个一维数组,那么我们要求的最大子矩形和就变成了求这个数组的最大字段和。最后枚举所有的行与行之间的组合即可。
建立一个三维数组,在读取的时候进行预处理,记录所有列的和情况,简化计算。
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
using namespace std;
int map[105][105];
int sum[105][105][105];
int N;
int cul(int a,int b)//计算a和b行之间最大子矩阵和
{
int q=-10000000;
int s=0;
for(int i=1;i<=N;i++)
{
if(s>=0)
{
s+=sum[a][b][i];
q=max(s,q);
}
else
{
s=0;
s+=sum[a][b][i];
q=max(s,q);
}
}
return q;
}
int main()
{
scanf("%d",&N);
for(int i=1;i<=N;i++)
{
for(int j=1;j<=N;j++)
{
scanf("%d",&map[i][j]);
for(int n=1;n<=i;n++)//预处理
{
if(i==1)sum[n][i][j]+=map[i][j];
else if(i>1)sum[n][i][j]=sum[n][i-1][j]+map[i][j];
}
}
}
int ans=-10000000;
for(int x=1;x<=N;x++)
{
for(int y=1;y<=x;y++)
{
ans=max(ans,cul(y,x));
}
}
cout<<ans;
}
POJ2018
这道题和第一道例题的区别是多了一个限制,就是子段大小不能小于F,要找平均数最大的子段。
我们可以设这个最大平均数为 G,使数组里每一个数都减去G,如果存在子段和不为负数且长度大于L的子段,那么说明这个G是可能成为正确答案的,但是也可能还有更大的G成为答案。
首先,为了避免精度问题,我们将所有读入的数都乘以1000,同时以long long 储存。
为了解决这个问题,我们在第一题的基础上引入二分的操作。通过二分G来缩短求解的时间。那么关键在于对于一个G,怎样去确定它是否可能是正确答案。
根据前面的分析,为了找到这个使G成立的子段,我们先用原数组减去G得到一个新数组c,然后我们可以先求得c中每一个元素的前缀和sum,代表该元素及其之间所有元素的和。对于每一个位置,枚举 i 从L 到 N ,min_val=min(min_val , sum[i-L] ),这样就相当于选出了以任意以 i 为结尾,以0<=k<=i-L为首的子段,其最大字段和为:sum[i]-min_val,而 k 具体在哪取决于min_val在哪儿取得最小,这样保证了该子段长度是大于L的。最后,更新一个 d=max(d,sum[i]-min_val),只要最后d>=0则说明合题。
然后再执行二分操作即可。代码如下:
#include<vector>
#include<cstdio>
#include<iostream>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<map>
#define maxn 100005
using namespace std;
typedef long long ll;
int N,F;
ll cow[maxn],c[maxn],sum[maxn];
int main()
{
scanf("%d %d",&N,&F);
for(int i=1;i<=N;i++){scanf("%lld",&cow[i]);cow[i]=cow[i]*1000;}
ll l=-1000000000,r=1000000000;
ll ans;
while(l<=r)
{
ll mid=(l+r)/2;
for(int k=1;k<=N;k++)c[k]=cow[k]-mid;
for(int h=1;h<=N;h++)sum[h]=sum[h-1]+c[h];
ll d=-1000000000,min_val=1000000000;
for(int i=F;i<=N;i++)
{
min_val=min(min_val,sum[i-F]);
d=max(d,sum[i]-min_val);
}
if(d>=0)
{
ans=mid;
l=mid+1;
}
else r=mid-1;
}
cout<<ans;
}