题目
给定由n个整数(可能有负数)组成的序列a1,a2,...,an,要在这n个数中选取相邻的一段ai,ai+1,...,aj(1≤i≤j≤n),使其和最大,输出最大的和。当所有整数均为负整数时,定义最大字段和为0。n个整数(可能有负数)组成的序列a_1,a_2,...,a_n,要在这n个数中选取相邻的一段a_i,a_{i+1},...,aj(1\leq i \leq j \leq n),使其和最大,输出最大的和。当所有整数均为负整数时,定义最大字段和为0。n个整数(可能有负数)组成的序列a1,a2,...,an,要在这n个数中选取相邻的一段ai,ai+1,...,aj(1≤i≤j≤n),使其和最大,输出最大的和。当所有整数均为负整数时,定义最大字段和为0。
例如,当{a1,a2,...,a8}\{a_1,a_2,...,a_8\}{a1,a2,...,a8} = {1,-3,7,8,-4,12,-10,6}时,最大字段和为23。令 b[j]=max1≤i≤j{∑k=ija[k]}(1≤j≤n),b[j] =\max_{1 \leq i \leq j} \{ \sum_{k=i}^j a[k]\} (1 \leq j \leq n),b[j]=1≤i≤jmax{k=i∑ja[k]}(1≤j≤n),则所求的最大字段和为:
max1≤i≤j≤n{∑k=ija[k]}==max1≤j≤n{max1≤i≤j{∑k=ija[k]}}=max1≤j≤n{b[j]}\max_{1 \leq i \leq j \leq n} \{ \sum_{k=i}^j a[k]\} == \max_{1 \leq j \leq n} \{ \max_{1 \leq i \leq j} \{\sum_{k=i}^j a[k]\} \} = \max_{1 \leq j \leq n} \{ b[j]\}1≤i≤j≤nmax{k=i∑ja[k]}==1≤j≤nmax{1≤i≤jmax{k=i∑ja[k]}}=1≤j≤nmax{b[j]}
其实上面定义看不懂并不影响理解,看懂下面的递归关系即可,上面写着只是为了显得更专业,好吧!我承认是课本的定义。
建立递归关系
当b[j−1]>0时,b[j]=b[j−1]+a[j],否则b[j]=a[j]。b[j-1]>0时,b[j] = b[j-1]+a[j],否则b[j] = a[j]。b[j−1]>0时,b[j]=b[j−1]+a[j],否则b[j]=a[j]。这句话还是十分简单的,要是前面的和大于0,那最大和一定要加上前面的正数。反之,若小于0则必须弃之。
b[j]状态转移方程为:
b[j]=max{b[j−1]+a[j],a[j]},(1≤j≤n)b[j] =\max \{ b[j-1] + a[j],a[j] \}, (1 \leq j \leq n)b[j]=max{b[j−1]+a[j],a[j]},(1≤j≤n)
代码
#include <stdio.h>
#define NUM 1001
int a[NUM];
int MaxSum(int n, int &besti, int &bestj)
{
int sum=0;
int b=0;
int begin = 0;//当b[i-1]小于等于0时,记录b[i]=a[i]的位置
for (int i = 1;i <= n;i++)
{
if (b>0) b+=a[i]; //状态转移方程
else {//当b<0时,更新到最新的位置
b=a[i];
begin = i;//开始位置
}
if (b>sum)
{
sum = b; //更新最优值
besti = begin;
bestj = i;
}
}
return sum;
}
int main()
{
int n;
int besti, bestj;
while (scanf("%d", &n) && n)
{
besti = 0; //初始化开头
bestj = 0; //初始化结尾
for (int i = 1; i <= n; i++)
scanf("%d", &a[i]);
printf("%d\n", MaxSum(n, besti, bestj));//输出最优值
printf("From %d to %d\n", besti, bestj);//输出最优解
}
return 0;
}
输入
8
1 -3 7 8 -4 12 -10 6
0
输出
23
From 3 to 6
总结
最大子矩阵和问题是最大字段和问题的推广,首先在这埋下一个坑,日后若遇到在加以学习吧!