贪心——数组操作

对于一道贪心题,关键是要证明,如果数据范围给好一点,估计很容易想到贪心
因为数据有点大,直接处理可能会TLE(虽然有人直接处理也过了,但秉着探索科学的心,我们来想想正解)
实际上我已经想过两两合并,但忽略了最长子段和跨越的情况

对于序列贪心问题,一定要想到合并两个小序列

这样才能线性递推


数组操作


基础题:
最大子段和

考虑直接扫描的过程遇到小于0就重新赋值为0,
则我们可以更新一个数组中间段的最大值(最大子段和)
当两个数组合并能产生最大值的时候,既满足最大前缀和和最大后缀和的值最大时可以更新最大值
然后我们考虑合并这两个数组,

1.最大前缀和

最大前缀和可以是后一个数组的最大前缀和(从第二个数组内不能从前往后选出和比最大前缀和的和),也可以是前一个数组的后缀和(从第一个数组从后往前不能选出和比最大后缀和还大的值)

2.最大后缀和

同理,分两种情况讨论

3.最大子段和

前一个数组最大子段和和后一个数组最大子段和,以及最大后缀和和最大前缀和的和最大值

  • 都不难理解,用反证法可以证明
  • 以3为例,首先在两个数组中,最大子串和肯定是两个数组的最大子段和
  • 考虑在两段合并之后的:
  • 若第一个数组不取最大后缀和,记取得的和为s1,最大后缀和为suf1,则s1<suf1,
  • 最大前缀和同理,s2<suf2
  • 则s1+s2<suf1+suf2
  • 矛盾,证毕
  sums=sum[quq[1]][1],pre[0]=sum[quq[1]][0],pre[1]=sum[quq[1]][2],ans=max(sum[quq[1]][1],max(pre[1],pre[0]));
    sumss=sum[quq[1]][3];
      for(int i=2;i<=m;i++){
	    int a=quq[i];
    	ans=max(max(ans,sum[a][1]),max(pre[1],pre[0]));
    	sums=max(max(pre[1]+sum[a][0],sum[a][1]),sums);
    	ans=max(ans,sums);
    	pre[0]=max(pre[0],sumss+sum[a][0]);
    	pre[1]=max(sum[a][2],pre[1]+sum[a][3]);
    	ans=max(ans,max(pre[0],pre[1]));
    	sumss+=sum[a][3];
    }


所以我们只需要维护最大前缀和,最大后缀和,最大子段和
两个数组,因为直接扫描的时候可以直接更新一个数组的最大前缀和,最大后缀和,最大子段和,数组(方便更新)
所以合并的时候也要维护这3个值
AC代码:

#include<bits/stdc++.h>
using namespace std;

const int N=51,L=5010,M=250010;
int n,m;int quq[M];int sum[N][4];int in[N][L];
int sums,pre[2],sumss;
int main(){
	  scanf("%d%d",&n,&m);
     for(int i=1;i<=n;i++){//
    	int l;
    	scanf("%d",&l);
    	int s=0;
    	for(int j=1;j<=l;j++)
    	{
    	    scanf("%d",&in[i][j]);	sum[i][3]+=in[i][j];
		}
        for(int j=1;j<=l;j++)
	   {		s+=in[i][j];
			sum[i][0]=max(sum[i][0],s);
       }
        s=0;
        for(int j=l;j>=1;j--)
       {
    	s+=in[i][j];
    	sum[i][2]=max(sum[i][2],s);
       }
       s=0;
       for(int j=1;j<=l;j++)
	   {
       	s+=in[i][j];
       	if(s<0) {s=0;continue;};
       	sum[i][1]=max(sum[i][1],s);
       }
	   }
    for(int i=1;i<=m;i++){scanf("%d",&quq[i]);}
    int ans;
    sums=sum[quq[1]][1],pre[0]=sum[quq[1]][0],pre[1]=sum[quq[1]][2],ans=max(sum[quq[1]][1],max(pre[1],pre[0]));
    sumss=sum[quq[1]][3];
      for(int i=2;i<=m;i++){
	    int a=quq[i];
    	ans=max(max(ans,sum[a][1]),max(pre[1],pre[0]));
    	sums=max(max(pre[1]+sum[a][0],sum[a][1]),sums);
    	ans=max(ans,sums);
    	pre[0]=max(pre[0],sumss+sum[a][0]);
    	pre[1]=max(sum[a][2],pre[1]+sum[a][3]);
    	ans=max(ans,max(pre[0],pre[1]));
    	sumss+=sum[a][3];
    }
    printf("%d",ans);
}

另一份AC代码(没看题解写的,调了很久)

这说明了最大前缀和,最大字段和的求法是不一样的,不可以一起求

因为最大子段和可以从任意位置开始,可以贪心策略,而最大前缀和不可以从任意位置开始,所以小于0时不能重新赋值为0

#include<bits/stdc++.h>
using namespace std;

const int N=51,L=5010,M=250010;
int n,m;int quq[M];int sum[N][4];int in[N][L];
int sums,pre[2],sumss;
int main(){
	  scanf("%d%d",&n,&m);
     for(int i=1;i<=n;i++){
    	int l;
    	scanf("%d",&l);
    	int s=0;
    	for(int j=1;j<=l;j++)
    	{
    	    scanf("%d",&in[i][j]);
			sum[i][3]+=in[i][j];
			s+=in[i][j];
			sum[i][1]=max(sum[i][1],s);
			if(s<0) {
				s=0;	
			}
			if(j==l){
			   sum[i][2]=max(sum[i][2],s);	
			}
    	}
        s=0;//分开求
        for(int j=1;j<=l;j++){
       	s+=in[i][j];
       	sum[i][0]=max(sum[i][0],s);
        }
        }
    for(int i=1;i<=m;i++){
    	scanf("%d",&quq[i]);
    }
    int ans;
    sums=sum[quq[1]][1],pre[0]=sum[quq[1]][0],pre[1]=sum[quq[1]][2],ans=max(sum[quq[1]][1],max(pre[1],pre[0]));
    sumss=sum[quq[1]][3];
      for(int i=2;i<=m;i++){
	    int a=quq[i];
    	ans=max(max(ans,sum[a][1]),max(pre[1],pre[0]));
    	sums=max(max(pre[1]+sum[a][0],sum[a][1]),sums);
    	ans=max(ans,sums);
    	pre[0]=max(pre[0],sumss+sum[a][0]);
    	pre[1]=max(sum[a][2],pre[1]+sum[a][3]);
    	ans=max(ans,max(pre[0],pre[1]));
    	sumss+=sum[a][3];
    }
    printf("%d",ans);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值