题意:求一个数列的最大连续和,并输出起止端。
题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=1003
——>>做这题三次了,似乎每次的写法都不一样,现在觉得,至少有两种想法可解决此题:
一、以数列的每个数为末端的最大连续和从左到右遍历一次。
二、分治与递归,[L, R)的最大连续和,取其中点M = L + (R-L) / 2,最大连续和要么在[L, M),要么在[M, R),要么横穿M。
两种想法都是dp……
直接dp:
#include <cstdio>
using namespace std;
const int maxn = 100000 + 10;
int a[maxn];
int main()
{
int T, N, i, cnt = 1;
scanf("%d", &T);
while(T--)
{
scanf("%d", &N);
for(i = 0; i < N; i++) scanf("%d", &a[i]);
int max_sum = a[0], max_L = 0, max_R = 0, cur_sum = a[0], L = 0, R = 0;
for(i = 1; i < N; i++)
{
if(cur_sum < 0)
{
cur_sum = a[i];
L = R = i;
}
else
{
cur_sum += a[i];
R = i;
}
if(cur_sum > max_sum)
{
max_sum = cur_sum;
max_L = L;
max_R = R;
}
}
printf("Case %d:\n", cnt++);
printf("%d %d %d\n", max_sum, max_L+1, max_R+1);
if(T) printf("\n");
}
return 0;
}
分治与递归dp:
#include <iostream>
using namespace std;
const int maxn = 100000 + 10; //1<=N<=100000
int A[maxn]; //A为输入数组
int dp(int L, int R, int &pre, int &suf) //找数组A中[L, R)上的最大连续和,其中最大连续和的左边界放入pre中,右边界放入suf中
{
if(R - L == 1) //如果只有一个元素,直接返回
{
pre = L;
suf = L;
return A[L];
}
int M = L + (R - L)/2, i, l_pre, l_suf, r_pre, r_suf; //取中值M进行分治
int max_left = dp(L, M, l_pre, l_suf); //求左串的最大连续和
int max_right = dp(M, R, r_pre, r_suf); //示右串的最大连续和
int max_pre = -214748364, max_suf = -214748364, sum = 0; //max_suf为左串的最大后缀和,max_pre为右串的最大前缀和
for(i = M-1; i >= L; i--) //求max_suf
{
sum += A[i];
if(sum >= max_suf) //更新
{
pre = i; //预先设[L, R)上的最大连续和横穿M,将其最大连续和的左边界更新
max_suf = sum;
}
}
sum = 0;
for(i = M; i < R; i++) //求max_pre
{
sum += A[i];
if(sum > max_pre) //更新
{
suf = i; //预先设[L, R)上的最大连续和横穿M,将其最大连续和的右边界更新
max_pre = sum;
}
}
sum = max_suf + max_pre; //横穿M的最大连续和
int MAX; //要返回的最大连续和
if(max_left >= sum) //当左子串的最大连续和大于等于横穿M的最大连续和时(注意不用>,因为相等的时候取先出现的)
{
pre = l_pre; //更新最大连续和的左边界
suf = l_suf; //更新最大连续和的右边界
MAX = max_left; //更新最大连续和
}
else
{
MAX = sum; //更新最大连续和
}
if(max_right > MAX) //当右子串的最大连续和大于横穿M的最大连续和时(注意不用>=,因为相等的时候取先出现的)
{
pre = r_pre; //更新最大连续和的左边界
suf = r_suf; //更新最大连续和的右边界
MAX = max_right; //更新最大连续和
}
return MAX; //返回最大连续和
}
int main()
{
int T, N, i, cnt = 1, l, r;
cin>>T;
while(T--)
{
cin>>N;
for(i = 1; i <= N; i++)
cin>>A[i];
cout<<"Case "<<cnt++<<":"<<endl;
int max_sum = dp(1, N+1, l, r);
cout<<max_sum<<" "<<l<<" "<<r<<endl;
if(T) cout<<endl;
}
return 0;
}
直接dp的Java版:
import java.util.Scanner;
public class Main {
final static int maxn = 100000 + 10;
static int a[] = new int[maxn];
public static void main(String[] args) {
int T, N, i, cnt = 1;
Scanner cin = new Scanner(System.in);
T = cin.nextInt();
while(T-->0){
N = cin.nextInt();
for(i = 0; i < N; i++) a[i] = cin.nextInt();
int max_sum = a[0], max_L = 0, max_R = 0;
int cur_sum = a[0], L = 0, R = 0;
for(i = 1; i < N; i++){
if(cur_sum < 0){
cur_sum = a[i];
L = R = i;
}
else{
cur_sum += a[i];
R = i;
}
if(cur_sum > max_sum){
max_sum = cur_sum;
max_L = L;
max_R = R;
}
}
System.out.println("Case "+(cnt++)+":");
System.out.println(max_sum+" "+(max_L+1)+" "+(max_R+1));
if(T>0) System.out.println();
}
cin.close();
}
}