D2. Red-Blue Operations (Hard Version)

题意

给定一个长度为\(n\)的数列和\(q\)次询问,初始所有数的颜色都是红色,每次询问给一个\(k\),要求你做恰好\(k\)次操作,对于第\(i\)次操作,你可以任选数列中的一个数

  1. 如果它是红色,则将它的值加上\(i\),并将它变成蓝色
  2. 如果它是蓝色,则将它的值减去\(i\),并将它变为红色

对于每个询问,求\(k\)次操作后数列最小值的最大值,\(1 \leq n,q \leq 1e5\)\(1 \leq a_i \leq 1e9\)\(1 \leq k_i \leq 1e9\)

题解

如果\(k \leq n\),显然,此时我们可以只加不减,且最优策略显然是把最大的数加到最小的数身上,次大的数加到次小的数身上,以此类推

如果\(k>n\)

我们不妨先考虑\(k-n\)是偶数的情况

首先证明这\(k\)次操作一定是\(+-+-+-+-...+++++\)这样的序列(加减在哪一个数上还不一定)

证明:我们交换序列中的任一对\(+-\),合法的交换一定是减法后移,加法前移(因为一开始所有数都是红色,所以对于序列的任一前缀,加法的数量必须不少于减法),在任一种分配策略下,我们这样交换一定会导致某些数减的更多或加的更少了,一定是不更优的

现在我们考虑这些操作分配给了哪些数,对于目前的最优序列,可以将\(k\)次操作分为两部分,后\(n\)次全为加法,前\(k-n\)次可以打包成\((k-n)/2\)次操作,每次操作相当于给任意一个数减去\(1\),由于操作的顺序不影响最后答案,我们不妨先做后\(n\)次操作

之前已经说明,如果\(k \leq n\),贪心即可,现在我们考虑后面还有若干次减法时,还能不能这样贪心

证明:考虑减法操作结束后,答案为\(min(原数列最小值,新数列和的平均值)\),如果我们在\(n\)次加法中不按最优策略来,原数列最小值一定不会更大,无论采取什么操作,新数列和的平均值一定,所以其它方案不会比贪心更优

所以,答案为\(min(n次操作后数列最小值,新数列和的平均值)\),直接计算单组询问复杂度是\(O(n)\)的,会\(TLE\),考虑如何优化

首先考虑\(k \leq n\)时,将原数列从小到大排序,我们本质上求的是\(min(\underset{1 \leq i \leq k}{min}a_i+k+1-i,\underset{k+1 \leq i \leq n}{min}a_i)\)

显然\(\underset{k+1 \leq i \leq n}{min}a_i=a_{k+1}\),这里我们可以令\(b_i=a_i-i+1\),令\(mi_i=\underset{1 \leq j \leq i}{min}b_i\),可以把上述式子变成求\(min(mi_k+k,a_{k+1})\),通过\(O(n)\)的预处理,我们可以\(O(1)\)的回答每组询问

求新数列的和,通过求和公式,显然也可以做到\(O(1)\),一点小细节时要注意\(k-n\)分奇偶讨论,具体公式见代码

代码

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll INF = 1e18;
const int N = 2e5 + 7;
ll n, q, sum, a[N], mi[N];
ll solve(ll k) {
	if (k <= n) {
		return min(mi[k] + k, a[k + 1]);
	}
	else if ((k - n) % 2) {
		ll op = (k - n + 1) / 2, presum = (k - n + 2 + k) * (n - 1) / 2;
		return min(min(mi[n - 1] + k, a[n]), (sum + presum - op) / n);
	}
	else {
		ll op = (k - n) / 2, presum = (k - n + 1 + k) * n / 2;
		return min(mi[n] + k, (sum + presum - op) / n);
	}
}
int main() {
	scanf("%lld%lld", &n, &q);
	for (ll i = 1; i <= n; ++i) {
		scanf("%lld", &a[i]);
		sum += a[i];
	}
	sort(a + 1, a + 1 + n);
	a[n + 1] = mi[0] = INF;
	for (ll i = 1; i <= n; ++i) {
		mi[i] = min(mi[i - 1], a[i] - i + 1);
	}
	while (q--) {
		ll k;
		scanf("%lld", &k);
		printf("%lld ", solve(k));
	}
	return 0;
}
posted @ 2023-05-13 15:05  DGJG  阅读(56)  评论(0)    收藏  举报