题面

$n$ 个数,第 $i$ 个数是 $a_i$ ,Bob 可以从第 $1$ 个数开始,依次考虑这些数选不选。

如果 Bob 同时选择了 $i,j(i<j)$ 两数则应该有 $a_i\le a_j$

如果 Bob 不选择 $i$ , 并且这是连续第 $k$ 场没有选择的数,他将丢失 $k$ 点分数。

Bob 最终获得的值是所有选择的数 $a_i$ 之和减去丢失的分数的和。求这个值最大值。

$n\leq10^5,|a_i|\leq10^9$

题解

$\mathcal{O}(n^2)$ 做法显然,考虑优化。如果没有 $a_i\le a_j$ 的限制就是直接斜率优化。有限制就在外面套层 CDQ 分治:按 $a_i$ 排序后 CDQ 分治,左半区间按原下标排序插入凸壳,右半区间按原下标顺序查询,查询时不断将队首弹出即可。时间复杂度 $\mathcal{O}(n\log n)$

代码

const int N = 1e5 + 5;

inline ll Read() {
	ll x = 0, f = 1;
	char c = getchar();
	while (c != '-' && (c < '0' || c > '9')) c = getchar();
	if (c == '-') f = -f, c = getchar();
	while (c >= '0' && c <= '9') x = (x << 3) + (x << 1) + c - '0', c = getchar();
	return x * f;
}

namespace Main {
	ll n;
	pair <ll, ll> a[N], t[N];
	ll f[N], q[N], c[N];
	double slope (ll x, ll y) { return (1.0 * c[x] - c[y]) / (y - x); }

	bool cmp (pair <ll, ll> a, pair <ll, ll> b) {return a.second < b.second;}
	void CDQ (ll l, ll r) {
		if (l == r) return;
		ll mid = (l + r) >> 1;
		CDQ (l, mid);
		ll p = q[0] = 1;
		for (ll i = l; i <= r; ++i) t[i] = a[i];
		sort(a + l, a + mid + 1, cmp);
		sort(t + l, t + r + 1, cmp);
		for (ll i = l, j = l; i <= r; ++i) {
			ll w = t[i].second;
			if (j <= mid && w == a[j].second) {
				while (p + 1 < q[0] && slope(q[q[0] - 2], w) > slope(q[q[0] - 1], w)) --q[0];
				q[q[0]++] = w;
				++j;
			} else {
				while (p + 1 < q[0] && q[p] * w + c[q[p]] <= q[p + 1] * w + c[q[p + 1]]) ++p;
				if (p < q[0]) {
					f[w] = max(f[w], f[q[p]] + t[i].first - (w - q[p]) * (w - q[p] - 1) / 2);
					c[w] = f[w] - w * (w + 1) / 2;
				}
			}
		}
	   	CDQ (mid + 1, r);
	}
	int main () {
		for (int t = Read(); t--; ) {
			n = Read();
			for (ll i = 1; i <= n; i++) {
				a[i] = make_pair(Read(), i);
			   	f[i] = a[i].first - (i - 1) * i / 2;
		        c[i] = f[i] - i * (i + 1) / 2;
			}
			sort (a + 1, a + 1 + n);
			CDQ(1, n);
			ll ans = -n * (n + 1) / 2;
			for (ll i = 1; i <= n; ++i) ans = max(ans, f[i] - (n - i) * (n - i + 1) / 2);
			printf("%lld\n", ans);
		}
		return 0;
	}
}

int main () {
	freopen("contest.in", "r", stdin);
	freopen("contest.out", "w", stdout);
	Main::main();
	return 0;
}
EOF

评论

暂无评论

发表评论

可以用@mike来提到mike这个用户,mike会被高亮显示。如果你真的想打“@”这个字符,请用“@@”。

博客信息

作者
Jayun
时间
2023-10-25 07:26:06
博客类型