原題連結:https://www.luogu.com.cn/problem/P1714
題意解讀:求長度不超過m的最大子段和
解題思路:
1、暴力法
設a[N]表示原陣列,s[N]是a[N]的字首和,對於每一個元素s[i],計算其與前m個元素之差,取差值最大值,用程式碼表示:
for(int i = 1; i <= n; i++)
{
for(int j = i - 1; j >= i - m && j >= 0; j--)
{
ans = max(ans, s[i] - s[j]);
}
}
76分程式碼:
#include <bits/stdc++.h>
using namespace std;
const int N = 500005;
int n, m;
int a[N], s[N];
int ans = INT_MIN;
int main()
{
cin >> n >> m;
for(int i = 1; i <= n; i++) cin >> a[i];
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 - 1; j >= i - m && j >= 0; j--)
{
ans = max(ans, s[i] - s[j]);
}
}
cout << ans;
return 0;
}
2、單調佇列最佳化
仔細觀察一下暴力法的程式碼:
此段程式碼可以改寫成:
for(int i = 1; i <= n; i++)
{
int minx = INT_MAX;
for(int j = i - 1; j >= i - m && j >= 0; j--)
{
minx = min(minx, s[j]);
}
ans = max(ans, s[i] - minx);
}
進一步觀察程式碼:
紅色框部分是要在長度為m的視窗內找最小值,因此可以採用單調佇列來最佳化
100分程式碼:
#include <bits/stdc++.h>
using namespace std;
const int N = 500005;
int n, m;
int a[N], s[N];
int ans = INT_MIN;
int q[N], head = 0, tail = -1;
int main()
{
cin >> n >> m;
for(int i = 1; i <= n; i++) cin >> a[i];
for(int i = 1; i <= n; i++) s[i] = s[i - 1] + a[i];
for(int i = 0; i <= n; i++) //需要注意的是,i需要從0開始,這樣佇列第一個元素可能是0,是為了保證區間和正確:s[r]-s[l-1]
{
while(head <= tail && i - q[head] > m) head++;
while(head <= tail && s[i] <= s[q[tail]]) tail--;
ans = max(ans, s[i] - s[q[head]]);
q[++tail] = i;
}
cout << ans;
return 0;
}