1. 程式人生 > >HDU-1231,最大區間連續和總結-分治法-dp

HDU-1231,最大區間連續和總結-分治法-dp

1、暴力列舉所有區間的連續和,維護最大和

int p1,p2,maxs=-INF;p1=p2=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(maxs<sum){maxs=sum;p1=i;p2=j;}
    }
}

2、因為求區間和的時候重複了所以可以用S[i,j]=S[1,j]-S[1,i-1]來避免重複

int p1,p2,maxs=-INF;p1=p2=0;
int S[N];S[0]=0;
for(int i=1;i<=n;i++){S[i]=S[i-1]+a[i];}
for(int i=1;i<=n;i++){
    for(int j=i;j<=n;j++){
        sum=S[j]-S[i-1];
        if(maxs<sum){maxs=sum;p1=i;p2=j;}
    }
}

3、分治法

分治法要求分而治之再合併;

分:分成三部分最大值,左,右,左and右;

治:左右可以遞迴求,左and右單獨求;

合併:取三者最大值即可;


定義【i,j】表示起點終點都在區間裡的最大和

將【i,j】上的最大和轉換成max(【i,m】,【m+1,j】,起點在前一個區間,終點在後一個區間的最大和)這三者中的最大值;

其中前兩個最大和可以遞迴實現,第三個定義的最大和一定是從中間向兩遍擴大區間的過程中的最大值,可以分別遍歷左右區間得到;

int maxsum(int* A, int x, int y){ //返回陣列在左閉右開區間[x,y)中的最大連續和
int v, L, R, maxs;
if(y - x == 1) return A[x]; //只有一個元素,直接返回
int m = x + (y-x)/2; //分治第一步:劃分成[x, m)和[m, y)
int maxs = max(maxsum(A, x, m),maxsum(A, m, y)); //分治第二步:遞迴求解
int v, L, R;
v = 0; L = A[m-1]; //分治第三步:合併(1)——從分界點開始往左的最大連續和Lfor(int i = m-1; i >= x; i——) L = max(L, v += A[i]);
v = 0; R = A[m]; //分治第三步:合併(2)——從分界點開始往右的最大連續和R
for(int i = m; i < y; i++) R = max(R, v += A[i]);
return max(maxs, L+R); //把子問題的解與L和R比較
}

4、動態規劃法

求一個區間最大和,要求每個子區間都取到最大和;滿足最優子結構;

子區間求最大和又是 重疊子問題;

所以可以用動態規劃求解;

首先如果我們要在O(n)的複雜度裡算出來,那狀態肯定是1維的即只枚區間的一個端點,那由對稱性,起點和終點是同等的,所以方便起見我們要列舉的是區間的終點。

所以定義狀態dp[i]是以i為區間的右端點的最大和;

先不管起點,如果我們知道了dp[i-1],dp[i]可以選擇跟i-1連起來(連起來就是dp[i-1]+a[i],不連那就只有自己了a[i]),這樣根據最大和我們知道“如果隊友太坑,就賣了不要”否則就跟上隊友;這裡如果dp[i-1]<0就沒必要連了

所以dp[i]=max(0,dp[i-1])+a[i];

然後我們要的答案就從各個終點的dp中選最大值就可以了;

for ( int i = 1 ; i <= n ; i++ )
{
    last = max(0,last)+a[i];
    ans = max(ans,last);
}


最後的問題是這樣定義狀態怎麼知道起點呢?

那就要從狀態轉移方程入手,如果連了i-1那起點相同,如果放棄了,就以自己為起點;

所以這道題可以這樣寫;

#include<cstdio>
#include<iostream>
using namespace std;
const int N=1e4+1;
const int INF=0x7fffffff;
int a[N];
int k;
int main(){
    //freopen("in.txt","r",stdin);
    while(cin>>k){
        if(k==0) break;
        int last,maxs,p1,p2,start=0;last=maxs=-INF;
        for(int i=1;i<=k;i++){
            scanf("%d",&a[i]);
            if(last<0){
                last=a[i];
                start=i;
            }
            else last+=a[i];
            if(last>maxs){
                p1=start;p2=i;
                maxs=last;
            }
        }
        if(maxs<0){printf("0 %d %d\n",a[1],a[k]);}
        else
        printf("%d %d %d\n",maxs,a[p1],a[p2]);
    }
    return 0;
}