欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

C语言程序设计100例之(13):最大子段和

程序员文章站 2022-06-11 17:17:52
例13 最大子段和 题目描述 给出一段序列,选出其中连续且非空的一段使得这段和最大。例如在序列2,-4,3,-1,2,-4,3中,最大的子段和为4,该子段为3,-1,2。 输入格式 第一行是一个正整数N,表示了序列的长度。 第二行包含N个绝对值不大于10000的整数Ai ,描述了这段序列。 输出格式 ......

例13        最大子段和

题目描述

给出一段序列,选出其中连续且非空的一段使得这段和最大。例如在序列2,-4,3,-1,2,-4,3中,最大的子段和为4,该子段为3,-1,2。

输入格式

第一行是一个正整数n,表示了序列的长度。

第二行包含n个绝对值不大于10000的整数ai ,描述了这段序列。

输出格式

一个整数,为最大的子段和是多少。子段的最小长度为1。

输入样例

7

2 -4 3 -1 2 -4 3

输出样例

4

        (1)编程思路。

        可以从长度为n的数列的最左端(设为数组元素a[1])开始扫描,一直到最右端(设为数组元素a[n-1])为止,记下所遇到的最大总和的子序列。

        程序中定义变量maxsum保存最大连续子段和,cursum保存当前连续子段和。

        初始时,cursum=a[0]、maxsum=a[0]。用循环for (i=1;i<n;i++)对序列中的每一个元素a[i]进行扫描处理。

        在这一扫描过程中,从左到右记录当前子序列的和(即cursum= cursum+a[i]),若这个和不断增加(即当前a[i]为正,从而使cursum+a[i]>maxsum成为可能),那么最大子序列的和maxsum也增加,从而更新maxsum。如果往右扫描中遇到负数,那么当前子序列的和cursum会减小,此时cursum将会小于maxsum,maxsum也就不更新;如果扫描到a[i]时,cursum降到0时,说明前面已经扫描的那一段就可以抛弃了,这时需要将cursum置为0。这样,cursum将从i之后的子段进行分析,若有比当前maxsum大的子段,需要更新maxsum。这样一趟扫描结束后,就可以得到正确结果。

        (2)源程序。

#include <stdio.h>

int main()

{

   int a[200001];

   int n,i,maxsum,cursum;

   scanf("%d",&n);

   for (i=0;i<n;i++)

       scanf("%d",&a[i]);

   cursum=a[0];

   maxsum=a[0];

   for (i=1;i<n;i++)

   {

       if (cursum+a[i]>maxsum)

       {

             maxsum=cursum+a[i];

       }

       if (cursum+a[i]<0)

       {

            cursum=0;

       }

       else

          cursum= cursum+a[i] ;

   }

   printf("%d\n",maxsum);

   return 0;

}

习题13

13-1  最大差值

        本题选自洛谷题库 (https://www.luogu.org/problem/p5146)

题目描述

hke最近热衷于研究序列,有一次他发现了一个有趣的问题:

对于一个序列a1 ,a2 ⋯an ,找出两个数i,j,1≤i<j≤n,使得aj −ai 最大。

现在给出这个序列,请找出aj −ai 的最大值。

输入格式

第一行为一个正整数n。

接下来n个整数,第k+1个整数为ak 。

输出格式

一行为 (aj −ai )的最大值

输入样例

10

1 3 4 6 7 9 10 1 2 9

输出样例

9

        (1)编程思路。

        由于求aj −ai 的最大值的要求是下标j>i,也就是说最大的数是在最小的数后面才符合要求,因此不能简单地求序列的一个最大数max和一个最小数min(无法保证max一定在min的后面),然后输出max-min作为答案。

        定义变量minnum保存序列的最小数,maxdiff保存最大差值,由于输入n>=2,因此可以先输入序列前两个数a和b,赋minnum的初值为a与b中的较小数,maxdiff的初值为b-a。

        然后用循环for (i=3;i<=n;i++)对序列中的每一个元素ai进行扫描处理。处理时,若当前ai与最小数minnum的差值大于maxdiff,则更新maxdiff,这个更新一定是有效的,因为保存的最小数minnum一定在当前数ai之前;若当前数ai比最小数小,则更新最小数minnum为当前数ai。

        这样一趟扫描结束后,就可以得到正确结果。

       (2)源程序。

#include <stdio.h>

int main()

{

    int minnum,maxdiff;

    int i,n,a,b;

    scanf("%d",&n);

    scanf("%d%d",&a,&b);

    minnum=a<b?a:b;

    maxdiff=b-a;

    for (i=3;i<=n;i++)

    {

        scanf("%d",&a);

        if (a<minnum) minnum=a;

        if (a-minnum>maxdiff) maxdiff=a-minnum;

    }

    printf("%d\n",maxdiff);

    return 0;

}

13-2  连续自然数和

题目描述

对一个给定的自然数m,求出所有的连续的自然数段,这些连续的自然数段中的全部数之和为m。

例子:1998+1999+2000+2001+2002 = 10000,所以从1998到2002的一个自然数段为m=10000的一个解。

输入格式

包含一个整数的单独一行给出m的值(10≤m≤2,000,000)。

输出格式

每行两个自然数,给出一个满足条件的连续自然数段中的第一个数和最后一个数,两数之间用一个空格隔开,所有输出行的第一个按从小到大的升序排列,对于给定的输入数据,保证至少有一个解。

输入样例

10000

输出样例

18 142

297 328

388 412

1998 2002

        (1)编程思路。

        定义两个变量left和right用于指示待求子序列的最左端和最右端,初始时令left=1,right=(int)sqrt(2.0*n),显然此时1+2+…+right的和值sum(sum=(right-left+1)*(left+right)/2)会非常接近n。之后进行如下操作过程:

        1)比较sum与n,根据sum与n的大小关系进行不同处理。简单描述就是若sum值大了,去掉子序列最左端的数,从而减少sum值;若sum值小了,将最右端数的下一个数加入子序列,从而增大sum值;若sum与n相等,则找到一个子序列的和等于n,输出此时子序列的left和right值,输出后和值sum中去掉子序列最左端的数以便继续向后找到其他的子序列。

        2)重复上面的过程,直到left==right,此时子序列中只有一个数,搜索结束,退出。

      (2)源程序。

#include <stdio.h>

#include <math.h>

int main()

{

     int left,right,sum,n;

     scanf("%d",&n);

     left=1;   right=(int)sqrt(2.0*n);

     sum=(right-left+1)*(left+right)/2;

     while (left<right)

     {

           if (sum==n)

           {

                    printf("%d %d\n",left,right);

                    sum-=left++;

           }

          else if (sum<n)

           {

                      right++;

                      sum+=right;

           }

           else

           {

                     sum-=left;

                     left++;

           }

     }

    return 0;

}

13-3  subsequence

本题选自北大poj 题库(http://poj.org/problem?id=3061)。

description

a sequence of n positive integers (10 < n < 100 000), each of them less than or equal 10000, and a positive integer s (s < 100 000 000) are given. write a program to find the minimal length of the subsequence of consecutive elements of the sequence, the sum of which is greater than or equal to s.

input

the first line is the number of test cases. for each test case the program has to read the numbers n and s, separated by an interval, from the first line. the numbers of the sequence are given in the second line of the test case, separated by intervals. the input will finish with the end of file.

output

for each the case the program has to print the result on separate line of the output file.if no answer, print 0.

sample input

2

10 15

5 1 3 5 10 7 4 9 2 8

5 11

1 2 3 4 5

sample output

2

3

        (1)编程思路。

         本题题意是:输入整数n和s,n表示数列中元素的个数,s表示一个和值,然后输入数列中的n个元素,求一个子序列长度最短的连续子序列,使子序列中全部元素的和大于等于s。输出这个子序列的长度。

        用两个变量i和j表示子序列的两个端点,初始时i和j的值均为0,和值sum=0。

        这个搜索过程简单描述为:先让右端点移动,并将右端点的值累加到和值sum中(语句为sum+=a[j++]),直到sum大于等于s,按要求更新答案;然后再让左端点移动,并将左端点的值从和值sum中减掉(语句为sum-=a[i++];),也随之更新答案,直到出现sum小于s后,右端点再移动。重复这个过程,直到右端点越过了原序列的最后一个数据。 

       (2)源程序。

#include <stdio.h>

int main()

{

     int t,i,j,a[100001],n,s,sum,minx;

     scanf("%d",&t);

     while(t--)

     {

        scanf("%d%d",&n,&s);

        for(i=0;i<n;i++)

           scanf("%d",&a[i]);

        i=0;

        minx=0x7fffffff;

        sum=0;

        for (j=0;j<n;)

        {

             while(sum<s && j<n)

                 sum+=a[j++];

             while(sum>=s)

            {

                 if (minx>j-i) minx=j-i;

                 sum-=a[i++];

            }

        }

        if (minx==0x7fffffff)

             minx=0;

        printf("%d\n",minx);

    }

    return 0;

}