最大子段和(51Nod 1049)、最小正子段和(51Nod 1065)、总结(最小子段和、最大子段和、最小正子段和)

2024-03-06 18:58

本文主要是介绍最大子段和(51Nod 1049)、最小正子段和(51Nod 1065)、总结(最小子段和、最大子段和、最小正子段和),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

最大子段和

一.问题描述

给定长度为n的整数序列,a[1...n], 求[1,n]某个子区间[i , j]使得a[i]+…+a[j]和最大.或者求出最大的这个和.例如(-2,11,-4,13,-5,2)的最大子段和为20,所求子区间为[2,4].

二.例题

51Nod 1049

N个整数组成的序列a[1],a[2],a[3],…,a[n],求该序列如a[i]+a[i+1]+…+a[j]的连续子段和的最大值。当所给的整数均为负数时和为0。

例如:-2,11,-4,13,-5,-2,和最大的子段为:11,-4,13。和为20。
Input
第1行:整数序列的长度N(2 <= N <= 50000)
第2 - N + 1行:N个整数(-10^9 <= A[i] <= 10^9)
Output
输出最大子段和。
Input示例
6
-2
11
-4
13
-5
-2
Output示例
20
思路:从前往后求和,若当前sum<0,那么sum+a[i]肯定小于a[i],所以这种情形下加上sum无意义,此时sum设为0.所以从前往后记录下sum最大的时候即可。

代码:

#include<stdio.h>
#include<string.h>
int main()
{int s[51000]; int n;while(~scanf("%d",&n)){for(int i=0;i<n;i++)scanf("%d",&s[i]);long long max=s[0],sum=s[0];for(int i=1;i<n;i++){if(sum<0)sum=0;sum+=s[i];if(sum>max)max=sum;}printf("%lld\n",max);} return 0;
}

三. 问题分析

题目简单,上网查一下发现有别人的总结,就记录下来了,尤其分治的思想,真的很值得学习。

1.穷举法

穷举应当是每个人都要学会的一种方式,这里实际上是要穷举所有的[1,n]之间的区间,所以我们用两重循环,可以很轻易地做到遍历所有子区间,一个表示起始位置,一个表示终点位置.代码如下:

    int start = 0;//起始位置  int end = 0;  //结束位置  int max = 0;  for(int i = 1; i <= n; ++i)  {  for(int j = i; j <= n;++j)  {  int sum = 0;  for(int k = i; k <=j; ++k)  sum += a[k];  if(sum > max)  {  start = i;  end = j;  max = sum;  }  }  }   

这个算法是几乎所有人都能想到的,它所需要的计算时间是O(n^3).当然,这个代码还可以做点优化,实际上我们并不需要每次都重新从起始位置求和加到终点位置.可以充分利用之前的计算结果.

或者我们换一种穷举思路,对于起点 i,我们遍历所有长度为1,2,…,n-i+1的子区间和,以求得和最大的一个.这样也遍历了所有的起点的不同长度的子区间,同时,对于相同起点的不同长度的子区间,可以利用前面的计算结果来计算后面的.

比如,i为起点长度为2的子区间和就等于长度为1的子区间的和+a[i+1]即可,这样就省掉了一个循环,计算时间复杂度减少到了O(n^2).代码如下:

int start = 0;//起始位置  
int end = 0;//结束位置  
int max = 0;  
for(int i = 1; i <= n; ++i)  
{  int sum = 0;  for(int j = i; j <= n;++j)  {  sum += a[j];  if(sum > max)  {  start = i;  end = j;  max = sum;  }  }  
}  

2.分治法

求子区间及最大和,从结构上是非常适合分治法的,因为所有子区间[start, end]只可能有以下三种可能性:

  • 在[1, n/2]这个区域内
  • 在[n/2+1, n]这个区域内
  • 起点位于[1,n/2],终点位于[n/2+1,n]内

以上三种情形的最大者,即为所求. 前两种情形符合子问题递归特性,所以递归可以求出. 对于第三种情形,则需要单独处理. 第三种情形必然包括了n/2和n/2+1两个位置,这样就可以利用第二种穷举的思路求出:

  • 以n/2为终点,往左移动扩张,求出和最大的一个left_max
  • 以n/2+1为起点,往右移动扩张,求出和最大的一个right_max
  • left_max+right_max是第三种情况可能的最大值

示例:

    int maxInterval(int *a, int left, int right)  {  if(right==left)  return a[left]>0?a[left]:0;  int center = (left+right)/2;  //左边区间的最大子段和  int leftMaxInterval = maxInterval(a,left,center);  //右边区间的最大子段和  int rightMaxInterval= maxInterval(a,center+1,right);  //以下求端点分别位于不同部分的最大子段和  //center开始向左移动  int sum = 0;  int left_max = 0;  for(int i = center; i >= left; –i)  {  sum += a[i];  if(sum > left_max)  left_max = sum;  }  //center+1开始向右移动  sum = 0;  int right_max = 0;  for(int i = center+1; i <= right; ++i)  {  sum += a[i];  if(sum > right_max)  right_max = sum;  }  int ret = left_max+right_max;  if(ret < leftMaxInterval)  ret = leftMaxInterval;  if(ret < rightMaxInterval)  ret = rightMaxInterval;  return ret;  }   

分治法的难点在于第三种情形的理解,这里应该抓住第三种情形的特点,也就是中间有两个定点,然后分别往两个方向扩张,以遍历所有属于第三种情形的子区间,求的最大的一个,如果要求得具体的区间,稍微对上述代码做点修改即可. 分治法的计算时间复杂度为O(nlogn).

3.动态规划法

动态规划的基本原理这里不再赘述,主要讨论这个问题的建模过程和子问题结构.时刻记住一个前提,这里是连续的区间

  • 令b[j]表示以位置 j 为终点的所有子区间中和最大的一个
  • 子问题:如j为终点的最大子区间包含了位置j-1,则以j-1为终点的最大子区间必然包括在其中
  • 如果b[j-1] >0, 那么显然b[j] = b[j-1] + a[j],用之前最大的一个加上a[j]即可,因为a[j]必须包含
  • 如果b[j-1]<=0,那么b[j] = a[j] ,因为既然最大,前面的负数必然不能使你更大

对于这种子问题结构和最优化问题的证明,可以参考算法导论上的“剪切法”,即如果不包括子问题的最优解,把你假设的解粘帖上去,会得出子问题的最优化矛盾.证明如下

  • 令a[x,y]表示a[x]+…+a[y] , y>=x
  • 假设以j为终点的最大子区间 [s, j] 包含了j-1这个位置,以j-1为终点的最大子区间[ r, j-1]并不包含其中
  • 即假设[r,j-1]不是[s,j]的子区间
  • 存在s使得a[s, j-1]+a[j]为以j为终点的最大子段和,这里的 r != s 
  • 由于[r, j -1]是最优解, 所以a[s,j-1]<a[r, j-1],所以a[s,j-1]+a[j]<a[r, j-1]+a[j]
  • 与[s,j]为最优解矛盾.

实例:

int max = 0;  
int b[n+1];  
int start = 0;  
int end = 0;  
memset(b,0,n+1);  
for(int i = 1; i <= n; ++i)  {  if(b[i-1]>0)  {  b[i] = b[i-1]+a[i];  }else{  b[i] = a[i];  }  if(b[i]>max)  max = b[i];  }  

动态规划法的计算时间复杂度为O(n),是最优的解。做几道题加深理解

最直白的LIS题:http://acm.hdu.edu.cn/showproblem.php?pid=1087

最大子段和升级版,最大M段和:http://acm.hdu.edu.cn/showproblem.php?pid=1024

原博客地址:http://blog.csdn.net/niteip/article/details/7444973


最小正子段和

一.例题

51Nod 1065 原题链接:https://www.51nod.com/onlineJudge/questionCode.html#!problemId=1065

题目思路:

a[i]=4,-1,5,-2,-1,2,6,-2  (i=0...n-1)

1.从前往后累加,即从a[0]累加到a[i]得到sum[i],得sum[i]=4,3,8,6,5,7,13,11。得到的该sum[i]值的意义是从0到i的这个子串中的和。如此我们能简单的想到从j(j=0...n-1)到i的子串遍历求和,该算法时间复杂度为O(n*n),对于该题肯定超时;

2.现在我用一个结构体数组node[i]来存储三个值——标号i、’原值num、累加和sum,然后对sum进行排序。排序后逐个遍历数组node[i],具体过程看代码:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int inf=0x3f3f3f3f;
const int maxn=50050;
struct Node
{int no;ll num;ll sum;
}node[maxn];
bool cmp(Node a,Node b)
{return (a.sum<b.sum)||(a.sum==b.sum&&a.no<b.no);
}
int main()
{int n;ll sum;scanf("%d",&n);sum=0;for(int i=0;i<n;i++){scanf("%lld",&node[i].num);sum+=node[i].num;node[i].no=i;node[i].sum=sum;}sort(node,node+n,cmp);ll minn=(node[0].num>0?node[0].num:inf);for(int i=1;i<n;i++)//遍历过程{if(node[i].no>node[i-1].no&&node[i].sum>node[i-1].sum)//若i-1的标号小于i的标号,{minn=min(node[i].sum-node[i-1].sum,minn);//取node[i-1].no+1...node[i].no这个子串if(node[i].sum>0)minn=min(node[i].sum,minn);if(node[i].num>0)minn=min(node[i].num,minn);}else{for(int j=i-2;j>=0;j--){if(node[i].no>node[j].no&&node[i].sum>node[j].sum)//从当前位置向前遍历,找到一个标号小于当前元素的{                                                //若找到的j的sum小于0,原sum减去它肯定比原sum大,minn=min(node[i].sum-node[j].sum,minn);    //所以无用,要继续找break;}}if(node[i].sum>0)minn=min(node[i].sum,minn);if(node[i].num>0)minn=min(minn,node[i].num);}}printf("%lld\n",minn);return 0;
}

下面来看看大神们总结的:

最小子段和,最大子段和,最小正子段和

最大子段和比较好求解,就是一个dp标记走就可以了,当dp<0,让dp等于当前位,dp大于0就加上当前位就好。


最小子段和就是把所有的元素去相反数,然后在此基础上求一个最大子段和就好。


主要想说的是最小正子段和的求法,是先算前i位的累加和dp[i],并记录标记为i。

然后对所有的dp[i]进行小到大的排序,然后对两个相连的dp[i],如果后者的标记大于前者的标记,那么它们的差值是候选答案,然后选取候选答案最小的那个。

PS:注意要在里面加一个标记为-1的,和为0的节点。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;
pair<ll,int>sum[55555];
int a[55555];
int main()
{int n,i,j;ll ans;cin>>n;ans=99999999;for(i=1;i<=n;i++) cin>>a[i];for(i=1;i<=n;i++) {sum[i].first=sum[i-1].first+a[i];sum[i].second=i;}sort(sum+1,sum+1+n);if(sum[1].first>0)ans=sum[1].first;for(i=2;i<=n;i++) {if(sum[i].first>0) ans=min(ans,sum[i].first);if(sum[i].second>sum[i-1].second && sum[i].first!=sum[i-1].first) ans=min(ans,sum[i].first-sum[i-1].first);}cout<<ans<<endl;return 0;
}

看大神的代码,哪里有啥罗里吧嗦的东西,自叹不如啊。

总结原址:http://blog.csdn.net/kevin_liu11/article/details/50324101

代码原址:http://blog.csdn.net/h1021456873/article/details/48921101

这篇关于最大子段和(51Nod 1049)、最小正子段和(51Nod 1065)、总结(最小子段和、最大子段和、最小正子段和)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/781012

相关文章

HarmonyOS学习(七)——UI(五)常用布局总结

自适应布局 1.1、线性布局(LinearLayout) 通过线性容器Row和Column实现线性布局。Column容器内的子组件按照垂直方向排列,Row组件中的子组件按照水平方向排列。 属性说明space通过space参数设置主轴上子组件的间距,达到各子组件在排列上的等间距效果alignItems设置子组件在交叉轴上的对齐方式,且在各类尺寸屏幕上表现一致,其中交叉轴为垂直时,取值为Vert

学习hash总结

2014/1/29/   最近刚开始学hash,名字很陌生,但是hash的思想却很熟悉,以前早就做过此类的题,但是不知道这就是hash思想而已,说白了hash就是一个映射,往往灵活利用数组的下标来实现算法,hash的作用:1、判重;2、统计次数;

poj 1258 Agri-Net(最小生成树模板代码)

感觉用这题来当模板更适合。 题意就是给你邻接矩阵求最小生成树啦。~ prim代码:效率很高。172k...0ms。 #include<stdio.h>#include<algorithm>using namespace std;const int MaxN = 101;const int INF = 0x3f3f3f3f;int g[MaxN][MaxN];int n

poj 1287 Networking(prim or kruscal最小生成树)

题意给你点与点间距离,求最小生成树。 注意点是,两点之间可能有不同的路,输入的时候选择最小的,和之前有道最短路WA的题目类似。 prim代码: #include<stdio.h>const int MaxN = 51;const int INF = 0x3f3f3f3f;int g[MaxN][MaxN];int P;int prim(){bool vis[MaxN];

poj 2349 Arctic Network uva 10369(prim or kruscal最小生成树)

题目很麻烦,因为不熟悉最小生成树的算法调试了好久。 感觉网上的题目解释都没说得很清楚,不适合新手。自己写一个。 题意:给你点的坐标,然后两点间可以有两种方式来通信:第一种是卫星通信,第二种是无线电通信。 卫星通信:任何两个有卫星频道的点间都可以直接建立连接,与点间的距离无关; 无线电通信:两个点之间的距离不能超过D,无线电收发器的功率越大,D越大,越昂贵。 计算无线电收发器D

poj 1734 (floyd求最小环并打印路径)

题意: 求图中的一个最小环,并打印路径。 解析: ans 保存最小环长度。 一直wa,最后终于找到原因,inf开太大爆掉了。。。 虽然0x3f3f3f3f用memset好用,但是还是有局限性。 代码: #include <iostream>#include <cstdio>#include <cstdlib>#include <algorithm>#incl

hdu 1102 uva 10397(最小生成树prim)

hdu 1102: 题意: 给一个邻接矩阵,给一些村庄间已经修的路,问最小生成树。 解析: 把已经修的路的权值改为0,套个prim()。 注意prim 最外层循坏为n-1。 代码: #include <iostream>#include <cstdio>#include <cstdlib>#include <algorithm>#include <cstri

git使用的说明总结

Git使用说明 下载安装(下载地址) macOS: Git - Downloading macOS Windows: Git - Downloading Windows Linux/Unix: Git (git-scm.com) 创建新仓库 本地创建新仓库:创建新文件夹,进入文件夹目录,执行指令 git init ,用以创建新的git 克隆仓库 执行指令用以创建一个本地仓库的

poj 3723 kruscal,反边取最大生成树。

题意: 需要征募女兵N人,男兵M人。 每征募一个人需要花费10000美元,但是如果已经招募的人中有一些关系亲密的人,那么可以少花一些钱。 给出若干的男女之间的1~9999之间的亲密关系度,征募某个人的费用是10000 - (已经征募的人中和自己的亲密度的最大值)。 要求通过适当的招募顺序使得征募所有人的费用最小。 解析: 先设想无向图,在征募某个人a时,如果使用了a和b之间的关系

poj 3258 二分最小值最大

题意: 有一些石头排成一条线,第一个和最后一个不能去掉。 其余的共可以去掉m块,要使去掉后石头间距的最小值最大。 解析: 二分石头,最小值最大。 代码: #include <iostream>#include <cstdio>#include <cstdlib>#include <algorithm>#include <cstring>#include <c