Abstract
本文主要介紹各種序列子段和問題。
P1 最大子段和
傳送門
Introduction
首先來看一道經典例題,求一段序列的最大子段和
Idea
考慮動態規劃,令 dp[i] 表示在取第 i 個數的情況下,前 i 個數所能得到的最大子段和,那麼顯然有 dp[i] = max( dp[i-1] + a[i] , a[i]),其中 a[i] 表示序列的第 i 個數。
Code
#include <bits/stdc++.h>
using namespace std;
int dp[300000];
int ans = -INT_MAX;
int main()
{
int n;
cin >> n;
memset(dp, -0x3f3f3f, sizeof dp);
for (int i = 1; i < n + 1; i++)
{
int m;
cin >> m;
dp[i] = max(dp[i - 1] + m, m);
ans = max(ans, dp[i]); // 注意答案每次都要更新
}
cout << ans;
return 0;
}
P2 雙子序列最大和
傳送門
Introduction
再看一道升級版的,這次我們需要找出兩個不重疊的子段,使他們的各自的子段和之和達到最大值。
Idea
一個顯然的想法是,我們可以依次列舉序列中的每一項,然後分別找出這一項右側和左側的最大子段和,然後將他們相加即可。這樣一來,問題就變得和 P1 極其相似了。
我們不妨以 l[i] 表示第 i 項左邊的數可以得到的最大子段和,r[i] 表示第 i 項右邊的數可以得到的最大子段和,答案就是 max( l[i] + r[i] ), i = 2,3...n-1。
下面考慮如何計算 l[i] 和 r[i] , 和 P1 類似的,我們可以寫出以下轉移方程 l[i] = max( l[i-1] + a[i-1], a[i-1]), r[i] = max ( r[i+1] + a[i+1] , a[i+1]),這樣一來,我們就成功得到了選取第 i-1/i+1 個數時,第 i 個數左側/右側的數所能取得的最大子段和,然後,我們對 l,r 陣列做以下處理:l[i] = max( l[i] , l[i-1] ),r[i] = max( r[i] , r[i+1] ),這樣一來,就將 l,r 陣列轉變為我們需要的東西了。
Code
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int maxn = 1100000;
int l[maxn];
int r[maxn];
int a[maxn];
signed main()
{
int n;
scanf("%lld", &n);
memset(l, -0x3f3f3f3f, sizeof l);
memset(r, -0x3f3f3f3f, sizeof r);
l[1] = l[0] = 0;
r[n] = r[n + 1] = 0;
scanf("%lld ", &a[1]);
for (int i = 2; i < n + 1; i++)
{
scanf("%lld", &a[i]);
l[i] = max(l[i - 1] + a[i - 1], a[i - 1]);
}
for (int i = n - 1; i > 0; i--)
{
r[i] = max(r[i + 1] + a[i + 1], a[i + 1]);
}
for (int i = n - 1; i > 0; i--)
{
r[i - 1] = max(r[i - 1], r[i]);
}
for (int i = 2; i < n + 1; i++)
{
l[i + 1] = max(l[i + 1], l[i]);
}
int ans = -0x3f3f3f3f;
for (int i = 2; i < n; i++)
{
ans = max(l[i] + r[i], ans);
}
cout << ans;
return 0;
}
P3 環狀最大兩段子段和
傳送門
Introduction
這一題是 P2 的進階版本,怎樣在環形序列中找到子段和最大的兩端不重疊序列呢?
Idea
實際上,可能的情況只有兩種(0表示不選,@表示選):
- 000@@@000@@@000
- @@@000@@@000@@@
第一種情況和 P2 是完全一致的,第二種情況看似有些變化,但仔細一想,選出和最大的 @ 和選出和最小的 0 好像是等價的啊!那麼我們只需要把序列中的數全部都取相反數,然後再重複 P2 的操作就可以了,此題得解。
Code
#include <bits/stdc++.h>
using namespace std;
int n;
int a[300000];
int dp_l[300000];
int dp_r[300000];
int sum;
bool ok;
int main()
{
scanf("%d", &n);
for (int i = 1; i < n + 1; i++)
{
scanf("%d", a + i);
if (a[i] > 0)
{
ok = 1;
}
sum += a[i];
}
// 如果全是負數的話,我們只需要把最大的兩個負數之和輸出就可以了
// 我不知道為啥程式會在這地方出問題所以加了特判qwq
if (!ok)
{
sort(a + 1, a + 1 + n);
cout << a[n] + a[n - 1] << endl;
return 0;
}
// 階段一
for (int i = 1; i <= n; i++)
{
dp_l[i] = max(dp_l[i - 1] + a[i], a[i]);
}
for (int j = n; j > 0; j--)
{
dp_r[j] = max(dp_r[j + 1] + a[j], a[j]);
}
// 階段二
for (int i = 1; i <= n; i++)
{
dp_l[i] = max(dp_l[i], dp_l[i - 1]);
}
for (int i = 1; i <= n; i++)
{
dp_r[i] = max(dp_r[i], dp_r[i + 1]);
}
// 更新答案
int ans1 = -INT_MAX;
for (int i = 2; i <= n; i++)
{
ans1 = max(ans1, dp_l[i - 1] + dp_r[i]);
}
// 全部取反
for (int i = 1; i <= n; i++)
{
a[i] *= -1;
}
// 階段一
for (int i = 1; i <= n; i++)
{
dp_l[i] = max(dp_l[i - 1] + a[i], a[i]);
}
for (int j = n; j > 0; j--)
{
dp_r[j] = max(dp_r[j + 1] + a[j], a[j]);
}
// 階段二
for (int i = 1; i <= n; i++)
{
dp_l[i] = max(dp_l[i], dp_l[i - 1]);
}
for (int i = 1; i <= n; i++)
{
dp_r[i] = max(dp_r[i], dp_r[i + 1]);
}
int ans2 = -INT_MAX;
for (int i = 2; i <= n; i++)
{
ans2 = max(ans2, dp_l[i - 1] + dp_r[i]);
}
// 把 ans2 變換為我們需要的東西
ans2 *= -1;
ans2 = sum - ans2;
// 輸出 ans1,ans2 中較大的
cout << max(ans1, ans2);
return 0;
}
Postscript
一些類似的題目:
LeeCode 918. 環形子陣列的最大和