最大子段和與單調佇列

2022-05-26 03:24:08 字數 3933 閱讀 1153

最大子段和是指,對於一段數列來說,有區間\([l,r]\)使得\(a_i+a_+...a_r\)最大,這個最大的和被稱為最大子段和。

擴充套件內容是求解最大子矩陣和。

通過列舉l和r,來列舉所有可能的情況,暴力計算l到r的所有和。

**:

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

for(int j=i;j<=n;j++)

時間複雜度\(o(n^3)\)

我們可以發現,其實在計算若干個數的和時,有重複計算,我們可以提前預處理字首和來避免這個問題,優化時間複雜度。

**:

for(int i=1;i<=n;i++) sum[i]=sum[i-1]+a[i];

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

for(int j=i;j<=n;j++)

maxx=max(maxx,sum[j]-sum[i-1]);

時間複雜度\(o(n^2)\)

2.2.1優化字首和演算法

我們發現,如果我們定住乙個左節點,我們的決策集合是只減少,不增多,這提示我們如果我們改而定住乙個右節點,那麼我們的決策集合應該是只增多不減少,這是乙個重要的性質,尤其是在dp中,可以使複雜度降低乙個量級,利用這個性質的方法是:我們用乙個變數來儲存前面所有決策集合的最小值,因為當我們定住乙個右端點,我們要找的是位於它左邊的乙個最小值。

**:

int minn=0,ans=-inf;

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

這個問題可以通過動態規劃來解決。

我們把第i+1個數接在後面。

我們讓第i+1個數自己成為乙個序列。

我們設\(f_i\) 表示以i結尾的最大子段和。

狀態轉移:\(f_i=max(f_+a_i,a_i)\)

最終答案為:\(max(f[i]),i=1,2,...n\)

可以發現,狀態\(f_i\)已經包括了所有的狀態空間,所以最終答案合法。

**:

for(int i=1;i<=n;i++) f[i]=max(f[i-1]+a[i],a[i]);

for(int i=1;i<=n;i++) maxx=max(maxx,f[i]);

時間複雜度:\(o(n)\)

思路:我們列舉兩行:\(l,r\),並把l到r之間的所有數,壓成乙個一維陣列,即\(c_i=\sum ^r_ a_\),跑一遍最大欄位和,對所有結果取max。

**:

#include#include#include#include#include#include#include#include#include#include#include#include#include#define dd double

#define ll long long

#define ull unsigned long long

#define n 101

#define m number

using namespace std;

const int inf=0x3f3f3f3f;

int n,a[n][n],sum[n][n],b[n],f[n],maxx=-inf;

inline int max(int a,int b)

int main()

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

for(int j=i;j<=n;j++)

} printf("%d",maxx);

return 0;

}

這個問題的基本思路是正著掃一遍,反著掃一遍,列舉斷點,求斷點在何處時和最大。

注意:列舉斷點時必要的,因為我們的結果一定能夠被分成兩個區間,而且這兩個區間一定是從1到n種列舉斷點後斷點兩邊區間的最大欄位和所在區間,否則就會有更優的答案

#include#include#include#include#include#include#include#include#include#include#include#include#include#define dd double

#define ld long double

#define ll long long

#define ull unsigned long long

#define n 50010

#define m number

using namespace std;

const int inf=0x3f3f3f3f;

int t,n,a[n],f[n][5];

inline int max(int a,int b)

int main()

for(int i=n;i>=1;i--)

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

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

} return 0;

}

乙個想法是在再我們原先dp的基礎上再加一維狀態,表示長度,但這樣即使可能可做(筆者還沒有實現),時間負責度仍然是 \(o(n^2)\)的,而我們dp做最大欄位和是\(o(n)\)的,我們能否將複雜度優化到\(o(n)\)呢?

我們有兩個思路,優化dp做法,優化字首和做法。

很明顯,如果我們要利用dp的話,條件「不超過m」明顯成了乙個附加狀態,但是如果我們選擇字首和的話,會發現這個條件實際上減少了字首和演算法(樸素演算法)的時間開支,進一步縮小了列舉的範圍。

所以我們嘗試優化字首和。

我們發現,當我們處理出字首和後,對於每乙個右端點r,我們都要找到乙個l,在符合兩者之間距離不超過m+1的前提下(因為是字首和,原陣列的m到這裡就是m+1)使得sum[l]最小。

簡單考慮一下就會發現,如果有乙個k,sum[k]大於等於sum[l],並且k小於l,那麼k這個決策就是完全無用的,他不可能對後面的狀態做貢獻,因為如果k能做貢獻的,l一定也能做貢獻,並且l還比k更優。

綜上,所以我們的決策集合一定是乙個下表單調遞增,sum單調遞增的序列,我們可以用乙個雙端佇列來儲存這個序列,即單調佇列。

單調佇列的思想是在決策集合中及時排除一定不是最優解的選擇,可以優化dp。

其主要步驟有三個:

排除隊頭超出m範圍的決策。

此時隊頭就是我們要取的值。

用此時的sum[i]去更新,不斷刪除隊尾決策,知道隊尾的值小於sum[i]

把sum[i]入隊。

注:**head為隊頭元素的前乙個元素,tail為隊尾元素

佇列中只存下標即可。

#include#include#include#include#include#include#include#include#include#include#include#include#include#define dd double

#define ld long double

#define ll long long

#define ull unsigned long long

#define n 300010

#define m number

using namespace std;

inline int max(int a,int b)

const int inf=0x3f3f3f3;

int q[n*3];

int n,m,a[n],sum[n];

int head,tail=1,maxx=-inf;

int main()

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

return 0;

}

使用單調佇列而不是用乙個變數來儲存資訊的原因是加上了限制條件「長度不超過m」。而能夠使用單調佇列條件是,決策集合是乙個區間,且區間中的所有不可能的決策去掉後是單調的。故能夠使用單調佇列來優化。

由於每乙個元素進出區間各只有一次,所以,維護單調佇列的時間負責度是\(o(n)\)的,也就是說,整個演算法的時間複雜度是\(o(n)\)

最大子序和 單調佇列

輸入乙個長度為n的整數序列,從中找出一段長度不超過m的連續子串行,使得子串行中所有數的和最大。輸入格式 第一行輸入兩個整數n,m。第二行輸入n個數,代表長度為n的整數序列。同一行數之間用空格隔開。輸出格式 輸出乙個整數,代表該序列的最大子序和。資料範圍 1 n,m 300000 輸入樣例 6 4 1...

最大子段和與最大子矩陣和

最大子段和。求一段連續的子段裡面最大的值。1 include2 const int maxn 10006 3 inta maxn 45 intmain 614 15for int i 1 i n i 20 printf d n ans 21 22return0 23 最大子矩陣和 求最大子矩陣的和,...

JOI(TYVJ)最大子序和 DP 單調佇列

輸入乙個長度為n的整數序列,從中找出一段不超過m的連續子串行,使得整個序列的和最大。例如 1,3,5,1,2,3 當m 4時,s 5 1 2 3 7 當m 2或m 3時,s 5 1 6 計算區間和的問題一般用字首和表示。先用s i 表示序列中前i項的和,然後s i s j 1 就可以表示i j的和 ...