poj 2479 最大子段和
poj 2479嚴格來說不是單純的最大子段和,它是乙個雙向的最大子段和,為了弄清雙向的最大子段和就必須弄清楚單向的最大子序和。
單向最大子段和問題如下:
在序列a[1..n](
任意乙個ai都是整數,有正有負
)中找出乙個子串行a[p..q]使得m=ap+ap+1+…+aq-1+aq最大
例如如下的序列:
1 7 -8 -4 5 10 -9 -5
最大的一段是從和為15
為使m最大,則a[p]和a[q]必須為正數,(在q-p>=1情況下)
如果a[p]為負數,那麼為使m最大,可以捨棄a[p],只取a[p+1..q]的序列(乙個數加上乙個負數會變小,要使和最大不如不要這個負數)a[q]必須為正也是同樣的道理。
再推廣結論,子串行a[p..q]中a[p..k]的
子子串行
和也必須為正(p<=k<=q),因為a[p..k]的和可以看成乙個數,它是在最優子串行的最左邊的,若它是負數,那麼a[k+1..q]的序列和將更大,同樣地,a[k+1,.q]必須為正。
於是就有了這樣的想法:
最優子串行a[p..q]一定是在a[1..n]中的,它的左邊a[1..p-1]一定是負值或不存在,它的右邊a[q+1..n]也一定是負值或不存在。但滿足左邊子子串行是負的,右邊子子串行也是負的的子串行不一定是最優的,我可以比較所有這些序列的和,取其中的最大值。
於是設乙個變數i來從左向右遍歷序列,並分別求出a[1..i]中最大子串行的和sum,用max來記錄子串行的最大值。
開始在i累加的過程中,使sum累加a[i]
那麼就有了兩種情況:
1.累加過程中sum<0 例如 a[1]為負值(此時sum=a[1]),那麼a[1]不可能是最優子串行的左邊部分,如果它是最優子串行的左邊部分,那麼它一定大於零,這裡產生了矛盾,此時就需要重置sum為0,從a[2]再開始累加,若a[2]也小於零,再重置為0,從a[3]開始累加,以此類推。
2.累加過程中sum>0 例如a[1]為正的(此時sum=a[1]),那麼a[1]就很有可能是最優子串行的左邊部分,將sum與max比較,維護max的值。若a[2]也是正的(sum=a[1]+a[2])也需要維護max,及時更新max的值。
在sum>0的情況中又會有兩種情況出現:
要加的下乙個值為負值
a.加上這個負數後sum仍然為正值,此時sum不會被置零,max值不會更新。(此時sum較之前變小了,max儲存的是前面計算的子串行的和)
b.加上這個負數後sum為負值,此時就要置零sum,重新來計算下乙個可能的最優子串行,此時max值儲存的是左邊的可能最優子串行的值。
以此進行下去,直到遍歷完整個序列。
sum的值維護了子串行的左邊界,max的值維護了子學列的右邊界,都來保證子串行的和最大。
上述分析可寫成下面的偽**:
sum = a[1] //初始化
max = -∞
for i <- 2 to n //2到n遍歷,初始化時已經算上a[1]了
if sum < 0 //sum小於零
sum = a[i] //重置sum
else //sum大於零
sum += a[i] //累加sum
if sum > max //維護max值
max = sum
簡化的c**:
sum = 0;
max = -∞;
for(i=1;i<=n;i++)
細節的地方:
有些地方往往沒有說序列有正有負,可能全是負數,此時就會出現乙個麻煩:上面的**執行完後,最大為0.
有可能序列的第乙個值就是負值,這時執行**時,sum不是被處理成了0就是被處理成了第二值,導致max值在i=1的時候不準確,這一點在需要打表的地方尤為重要,然而poj 2479正是在這裡為難大家。
那麼應該怎麼改呢?
既然只是第乙個值的問題,那麼就可以對第乙個值進行特殊處理,令max=a[1],就能解決這個問題
於是改過的**就是:
sum = 0;
max = a[1];
for(i=1;i<=n;i++)
來看一看上下兩段**處理這兩個序列的不同結果:
1.-3 4 6 1 -3
未注意細節的
注意了細節的
summax
summax
-3->0
-∞-3->0-34
0->4
40->4
104->10
104->10
1110->11
1110->11911
911對結果無太大影響,最終都是11
2.-2 -3 -5 -1 -4
未注意細節的
注意了細節的
summax
summax
-2->0
0-2->0
-2-3->0
0-3->0
-2-5->0
0-5->0
-2-1->0
0-1->0
-1-4->0
0-4->0-1
如果碰上了全負的序列,未注意細節的演算法就是一場災難。
poj 2479中就需要注意這樣的細節,雙向的最大子序和問題關鍵在於分割,可將序列從a[k]處分開成a[1..k],a[k+1..n](1
在實際的過程中,先會遍歷k從1到n-1,求出a[1.k]的最大子序和,打乙個表比方說叫left[k],再遍歷k從n到2,求出a[k..n]的最大子序和,再打乙個表比方叫right[k],再遍歷k從1到n-1,求出最大的left[k]+right[k+1]
轉移方程為:
res = max
ac**:
#include int data[50005];//資料
int left[50005];//left[i]表示從1到i的最大子序和值
int right[50005];//right[i]表示從i到n的最大子序和值
int main()
left[j] = max;
} //--------------------------------
//-----------計算右邊--------------
max = data[n];
sum = data[n];
right[n] = data[n];
for(j=n-1;j>=1;j--)
right[j] = max;
} //-------------------------------
max = -(1<<30);
for(j=1;j<=n-1;j++)
printf("%d\n",max); }
return 0;
}
POJ 2479的簡單想法
這道題是簡單的dp例題拓展,問題是 給定一行數字,然後求這行數字中兩個連續子串行和的最大值,並輸出 兩個子串行不能有重疊的部分 由於是初學者,可能想法比較侷限。我的總體思路是這樣 這題肯定和最大求子序列有關,然後進行工作 用dp1 i 表示以第i個數結尾的最大子串行的和,用dp i 表示已第i個數為...
poj2479分步DP精簡成O n 演算法
提取資訊包括從s到t之間的和 但是n較大這樣dp i j 的形式顯然不行,那麼如果轉換 其實我們除了記錄 i,j 之間的和那麼可以記錄以i開頭和以i結尾兩種情況 後來發現根本不用記錄以什麼為開頭或者結尾,每次更新前面一段值就行 725k 438ms include using namespace s...
最大子段和
設a 是n個整數的序列,稱為該序列的子串行,其中1 i j n.子串行的元素之和稱為a的子段和.例如,a 2,11,4,13,5,2 那麼它的子段和是 長度為1的子段和 2,11,4,13,5,2 長度為2的子段和 9,7,9,8,7 長度為3的子段和 5,20,4,6 長度為4的子段和 18,15...