版权声明:本文为博主原创文章,转载请标明出处 https://blog.csdn.net/MOMING_V/article/details/83241001
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;
}