← 全部文章

CF573E Bear and Bowling 题解

本文目录(3 节)
  1. 题意
  2. 题解
  3. 代码

CF573E Bear and Bowling

题意

  • 给定一个长度为 nn 的序列 a1…na_{1\dots n}。
  • 你要求一个 aa 的子序列 b1…mb_{1\dots m}(可以为空),使得 ∑i=1mibi\sum_{i=1}^m ib_i 的值最大。
  • n≤105n \le 10^5,∣ai∣≤107|a_i| \le 10^7。

题解

首先有个显然的贪心:每次选择贡献最大的位置,直到贡献非正,此时贡献和即为答案。

这里的贡献可以简单的写成 kiai+bik_ia_i+b_i,其中 kik_i 为 ii 之前被选择的位置数 +1+1,bib_i 表示 ii 之后被选择的位置的 aa 之和。

相当于我们要维护 nn 个一次函数 kiai+bik_ia_i + b_i,支持区间 ki,bik_i,b_i 加和全局最大值。

这玩意儿是有办法 polylog\text{polylog} 维护的(详见 EI 的提交),但非常复杂。

考虑分块,块内使用单调队列维护一个上凸壳。

对于每次最大的位置 pp,先将 pp 的贡献计入答案,然后删除 pp(将 pp 的贡献设为 −∞-\infty)。

对于 pp 之前的位置 ii,相当于 bib_i 加上 apa_p;对于 pp 之后的位置 ii,相当于 kik_i 加上 11。

对于每一整块,打上两个分别关于 k,bk,b 的标记即可;而对于 pp 所在的块,暴力重构即可。

总时间复杂度 O(nn)\mathcal O(n \sqrt n)。

代码

const int N = 1e5 + 7, M = 370;
const ld inf = 1e18L;
int n, m, k, a[N], b[N], p[N], l[M], r[M];
ll f[N], ans;

struct T {
	int l, r, q[M];
	ll k, b;
	inline ll c(int i) {
		return f[i] + k * a[i] + b;
	}
	inline ld s(int i, int j) {
		if (a[i] == a[j]) return f[i] > f[j] ? -inf : inf;
		return 1.0L * (f[i] - f[j]) / (a[i] - a[j]);
	}
	inline void build(int x) {
		for (int i = ::l[x]; i <= ::r[x]; i++) f[i] += k * a[i] + b;
		k = b = l = r = 0;
		for (int i = ::l[x]; i <= ::r[x]; i++) {
			while (r - l > 1 && s(q[r-2], q[r-1]) < s(q[r-1], p[i])) --r;
			q[r++] = p[i];
		}
	}
	inline pair<ll, int> ask() {
		while (r - l > 1 && c(q[l]) <= c(q[l+1])) ++l;
		return mp(c(q[l]), q[l]);
	}
} t[M];

int main() {
	rd(n), m = sqrt(n);
	for (int i = k = 1; i <= n; k += !(i % m), i++)
		rd(a[i]), f[i] = a[i], p[i] = i, b[i] = k;
	if (!(n % m)) --k;
	for (int i = 1; i <= k; i++) {
		l[i] = r[i-1] + 1, r[i] = min(i * m, n);
		sort(p + l[i], p + r[i] + 1, [&](int i, int j) { return a[i] < a[j]; });
		t[i].build(i);
	}
	while (1) {
		pair<ll, int> o = mp(0ll, 0);
		for (int i = 1; i <= k; i++) o = max(o, t[i].ask());
		if (!o.fi) return print(ans), 0;
		ans += o.fi, f[o.se] = -inf;
		for (int i = 1; i < b[o.se]; i++) t[i].b += a[o.se];
		for (int i = l[b[o.se]]; i < o.se; i++) f[i] += a[o.se];
		for (int i = o.se + 1; i <= r[b[o.se]]; i++) f[i] += a[i];
		for (int i = b[o.se] + 1; i <= k; i++) ++t[i].k;
		t[b[o.se]].build(b[o.se]);
	}
	return 0;
}

标签:凸包、分块、单调队列、贪心

评论

    填写昵称即可留言。评论经站主审核后公开,请勿提交私人信息。

    正在连接评论服务…