@bzoj - 4385@ [POI2015] Wilcze doły
@description@
给定一个长度为 n 的序列,你有一次机会选中一段连续的长度不超过 d 的区间,将里面所有数字全部修改为 0。
请找到最长的一段连续区间,使得该区间内所有数字之和不超过 p 。
input
第一行包含三个整数 n, p, d (1 <= d <= n <= 2000000,0 <= p <= 10^16)。
第二行包含n个正整数,依次表示序列中每个数 w[i] (1 <= w[i] <= 10^9)。
output
包含一行一个正整数,即修改后能找到的最长的符合条件的区间的长度。
sample input
9 7 2
3 4 1 9 4 1 7 1 3
sample output
5
@solution@
如果选择的区间长度 <= d,则可以把这个区间改成全零区间,它一定合法。
也就是说最终区间的长度一定 >= d。
如果选择的区间长度 >= d,贪心地想,我们肯定是修改这个区间内长度为 d 且权值和最大的子区间。
我们用 s[l...r] 表示 l ~ r 的权值和。对于每一个 i,我们再记录一个 f[i] = s[i-d+1 ... i]。
如果我们选择了区间 [l, r],它对答案的贡献为 s[l...r] - max{f[l+d-1] ... f[r]}。
使用滑动窗口求解答案,并使用单调队列维护上面的 max。
@accepted code@
#include<cstdio>
#include<algorithm>
using namespace std;
typedef long long ll;
const int MAXN = 2000000;
ll S[MAXN + 5], val[MAXN + 5];
inline ll GetSum(int le, int ri) {
return S[ri] - S[le-1];
}
int que[MAXN + 5], s = 1, t = 0;
int read() {
int x = 0; char ch = getchar();
while( ch > '9' || ch < '0' ) ch = getchar();
while( '0' <= ch && ch <= '9' ) x = 10*x + ch-'0', ch = getchar();
return x;
}
int main() {
int n, d; ll p;
scanf("%d%lld%d", &n, &p, &d);
for(int i=1;i<=n;i++)
S[i] = S[i-1] + read();
for(int i=d;i<=n;i++)
val[i] = GetSum(i-d+1, i);
int ri = d-1, ans = d;
for(int le=1;ri<n;le++) {
while( s <= t && que[s]-le+1 <= d )
s++;
while( ri < n ) {
if( ri-le+1 < d || GetSum(le, ri+1) - max(val[que[s]], val[ri+1]) <= p ) {
ri++;
while( s <= t && val[que[t]] <= val[ri] )
t--;
que[++t] = ri;
}
else break;
}
ans = max(ans, ri-le+1);
}
printf("%d\n", ans);
}
@details@
垃圾题目居然卡我常数。不写读入优化还过不了……