10月20日備戰Noip2018模擬賽10 T1 Max 和最大
阿新 • • 發佈:2018-12-16
10月20日備戰Noip2018模擬賽10
T1 Max 和最大
題目描述
Cyf的黑題,偏題,怪題,黑科技題、大碼農題都做膩了,於是她想做一下籤到水題,她希望從有一個長度為N的整數序列(a1,a2,…,an)中找出一段連續的長度不小於A,且不超過B的子序列,使得這個子序列的和最大。
輸入格式
第一行三個整數N,A,B(1<=A<=B<=N)。第二行為N整數,每個整數用空格隔開,表示該整數序列。
輸出格式
一個整數,為cyf輕易求出的最大子序和。
輸入樣例
6 3 5
1 -3 5 1 -2 3
輸出樣例
7
樣例解釋
選從第三個數開始往後連續的四個數。這四個數的和是5+1+(-2)+3=7
資料範圍
對於30%的資料,N<=1000
對於另外30%的資料, A = 1且 B = n。
對於100%的資料,N<=500000
思路
DP + 單調佇列
1.由求區間值,轉移到求字首和的差。 f[i]表示前i項的和,那麼區間[i,j]的和即為f[i]-f[j-1],這樣就簡化了問題。
2. 對於以i結尾的某個序列,j為前端點,則 i-b+1<=j<=i-a+1。 所以以i結尾的序列和的最優值為 f[i]-f[j-1]
3.對轉移過程進行優化,記錄 i-b+1<=j<=i-a+1之間最小的值。這樣就維護一個單調佇列,把複雜度降為O(N)。
60分程式碼(不用單調佇列)
#include <iostream> #include <cstdio> using namespace std; const int maxn = 500005; const int INF = -0x7fffffff; int n, x[maxn], a, b, tmp, ans = INF, s[1001]; int main() { scanf("%d%d%d", &n, &a, &b); for (int i = 1; i <= n; i ++){ scanf("%d", &x[i]); } if (n <= 1000){ for (int i = 1; i <= n; i ++){ s[i] = s[i - 1] + x[i]; } for (int i = 0; i <= n; i ++){ for (int j = i + a; j <= min(i + b, n); j ++){ ans = max(ans, s[j] - s[i]); } } } else{ for (int i = 1; i <= n; i ++){ if (x[i] + tmp < 0) tmp = 0; else tmp += x[i]; if (tmp > ans) ans = tmp; } } printf("%d", ans); return 0; }
程式碼
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
const int N = 500001;
const int INF = -0x7fffffff;
int ans = INF;
int i, j, k, n, m, a, b, t, w, p;
int d[N], f[N], s[N];
int main()
{
freopen ("max.in", "r", stdin);
freopen ("max.out", "w", stdout);
cin >> n >> a >> b;
for (i = 1; i <= n; i ++){
cin >> k;
f[i] = f[i - 1] + k;
if(i >= a){
w ++;
d[w] = f[i - a];
s[w] = i - a;
if (i - s[t] + 1 > b) t ++;
p = w - 1;
for (j = p; j >= t; j --){
if(d[w] < d[j]){
d[j] = d[w];
s[j] = s[w];
w = j;
}
else break;
}
ans = max (ans, f[i] - d[t]);
}
}
cout << ans;
fclose(stdin);
fclose(stdout);
return 0;
}