最大子段和

洛谷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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值