@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@

垃圾题目居然卡我常数。不写读入优化还过不了……

posted @ 2019-02-06 17:52  Tiw_Air_OAO  阅读(114)  评论(0编辑  收藏  举报