2019HDU多校第三场 Distribution of books 二分 + DP

题意:给你一个序列,你可以选择序列的一个前缀,把前缀分成k个连续的部分,要求这k个部分的区间和的最大值尽量的小,问这个最小的最大值是多少?

思路:首先看到最大值的最小值,容易想到二分。对于每个二分值mid,我们判断原序列是否可以构成k个区间和小于等于mid的区间,这个可以用DP来做。我们先求出序列的前缀和,这样可以把区间和变成前缀相减的形式。之后,对于当前的前缀和sum[i], 我们找前缀和大于等于sum[i] - mid的状态中dp值最大的向自己转移。如果最后存在状态的dp值大于等于k,那么说明二分的mid值偏大,要缩小上边界。反之亦然。dp用离散化 + 线段树优化,总复杂度O(n * logn) * log(值域)

代码:

#include <bits/stdc++.h>
#define LL long long
#define ls (o << 1)
#define rs (o << 1 | 1)
using namespace std;
const int maxn = 200010;
LL sum[maxn];
LL a[maxn];
LL b[maxn];
int dp[maxn], mx[maxn * 4];
int n, m, tot;
void build(int o, int l, int r) {
	if(l == r) {
		mx[o] = -1;
		return;
	}
	int mid = (l + r) >> 1;
	build(ls, l, mid);
	build(rs, mid + 1, r);
	mx[o] = max(mx[ls], mx[rs]);
}
void update(int o, int l, int r, int p, int val) {
	if(l == r) {
		mx[o] = max(mx[o], val);
		return;
	}
	int mid = (l + r) >> 1;
	if(p <= mid) update(ls, l, mid, p, val);
	else update(rs, mid + 1, r, p, val);
	mx[o] = max(mx[ls], mx[rs]);
}
int query(int o, int l, int r, int ql, int qr) {
	if(ql > qr) return -1;
	if(l >= ql && r <= qr) {
		return mx[o];
	}
	int mid = (l + r) >> 1, ans = -1;
	if(ql <= mid) ans = max(ans, query(ls, l, mid, ql, qr));
	if(qr > mid) ans = max(ans, query(rs, mid + 1, r, ql, qr));
	return ans;
}
bool solve(LL mid) {
	build(1, 1, tot);
	update(1, 1, tot, lower_bound(b + 1, b + 1 + tot, 0) - b, 0);
	for (int i = 1; i <= n; i++) {
		int p = lower_bound(b + 1, b + 1 + tot, sum[i] - mid) - b;
		int p1 = lower_bound(b + 1, b + 1 + tot, sum[i]) - b;
		int tmp =  query(1, 1, tot, p, tot);
		if(tmp == -1) dp[i] = -1;
		else dp[i] = tmp + 1;
		if(dp[i] >= m) return 0;
		update(1, 1, tot, p1, dp[i]);
	}
	return 1;
}
int main() {
	int T;
//	freopen("input.txt", "r", stdin);
//	freopen("1.in", "r", stdin);
	scanf("%d", &T);
	while(T--) {
		scanf("%d%d", &n, &m);
		for (int i = 1; i <= n; i++) {
			scanf("%lld", &a[i]);
			sum[i] = sum[i - 1] + a[i];
			b[i] = sum[i];
		}
		b[n + 1] = 0;
		sort(b + 1, b + 1 + n + 1);
		tot = unique(b + 1, b + 1 + n + 1) - (b + 1);
		LL l = -2e14, r = 2e14;
		while(l < r) {
			LL mid = (l + r) >> 1;
			if(solve(mid)) l = mid + 1;
			else r = mid;
		}
		printf("%lld
", l);
	}
} 

  

原文地址:https://www.cnblogs.com/pkgunboat/p/11267824.html