B. The Tortoise and the Hare
给定一个长度为nnn的数组a,(1≤ai<m)a, (1 \leq a_i < m)a,(1≤ai<m),mmm是一个给定的数(1≤m≤109)(1 \leq m \leq 10 ^ 9)(1≤m≤109),有QQQ次操作,分为两类:
- 给定u,v,(1≤u≤n,1≤v<m)u, v,(1 \leq u \leq n, 1 \leq v < m)u,v,(1≤u≤n,1≤v<m),把数组上aua_uau的值改为vvv。
- 给定l,r,k,(1≤l≤r≤n,1≤k≤r−l)l, r, k, (1 \leq l \leq r \leq n, 1 \leq k \leq r - l)l,r,k,(1≤l≤r≤n,1≤k≤r−l),每次对[l,r][l, r][l,r]区间内前r−l+1−kr - l + 1 - kr−l+1−k小的数都+1+ 1+1,问最多能进行多少次操作, 使得i∈[l,r],ai<mi \in[l, r], a_i < mi∈[l,r],ai<m恒成立。
对于操作一,我们直接修改即可,如何计算询问二呢,我们思考如下一个问题:
给定一个长度为nnn的数组aaa,每次我们要选择kkk个不同的数,把这kkk个数的值都减111,问我们最多能进行几次操作。
这个问题考虑最优解法,其实就是每次拿前kkk大,然后对这kkk个数都减111,知道不能进行操作为止。
不难发现其实这个问题,跟我们的询问二操作是几乎一样的,如果我们对询问二中的每个aia_iai都存m−ai−1m - a_i - 1m−ai−1的值,这个问题就变得跟上述问题是一样的了。
考虑如何快速解决这个问题(因为我们不能对每次询问都进行模拟),尝试二分求解:
假设我们当前二分区间为[l,r][l, r][l,r],判断答案是在[l,mid][l, mid][l,mid]或者[mid+1,r][mid + 1, r][mid+1,r]区间,所以我们需要判断x=mid+1x = mid + 1x=mid+1是否是可行的。
对于那些ai≥xa_i \geq xai≥x的数来说,如果每次删除我们都选他,可以发现对整个过程的模拟是没有影响的,假设这样的数有cntcntcnt个。
接下来我们的问题就转换成为,当所有数都<x< x<x,我们每次需要挑选k−cntk - cntk−cnt个数然后对其−1-1−1,能否进行xxx次操作了。
我们再对这个问题进行转换,如果我们要进行xxx次操作,每次我们最多能选多少个数,如果每次可选的数≥k−cnt\geq k - cnt≥k−cnt,则说明合法。
由于ai<xa_i < xai<x,我们进行了xxx次操作,说明,我们可以满足在同一次操作中不会出现两个一样的,所以每次可选的数为sumx\frac{sum}{x}xsum。
由此我们解决了,询问操作,如果是静态问题,可以考虑直接主席树上二分,但是这里是一个带修的问题,所以可以考虑树套树,
如果是树状数组套主席树,空间复杂度将会是O(nlog2n)O(n \log ^ 2 n)O(nlog2n)的,不可行,所以得通过权值线段树套平衡树来解决,达到空间O(nlogn)O(n \log n)O(nlogn)的。
其实还有一个代码量小,同时满足时间复杂度的离线做法,整体二分,空间O(n)O(n)O(n),时间O(nlog2n)O(n \log ^ 2 n)O(nlog2n)。
#include <bits/stdc++.h>using namespace std;const int N = 1e5 + 10;int n, m, T, cnt, a[N];long long ans[N];inline int lowbit(int x) {return x & (-x);
}struct BIT {long long sum[N];void update(int rt, int x) {while (rt <= n) {sum[rt] += x;rt += lowbit(rt);}}long long query(int rt) {long long ans = 0;while (rt) {ans += sum[rt];rt -= lowbit(rt);}return ans;}
}sum, num;struct Res {int op, id, l, r, k;/*op = 0, modify a_l + rop = 1, query [l, r], k*/long long pre_sum, pre_num;}q[N << 2], ql[N << 2], qr[N << 2];void solve(int L, int R, long long l, long long r) {if (L > R) {return ;}if (l == r) {for (int i = L; i <= R; i++) {if (!q[i].op) {ans[q[i].id] = l;}}return ;}long long mid = l + r >> 1;int cnt1 = 0, cnt2 = 0;for (int i = L; i <= R; i++) {if (q[i].op) {if (q[i].r <= mid) {sum.update(q[i].l, q[i].r * q[i].op), num.update(q[i].l, q[i].op);ql[++cnt1] = q[i];}else {qr[++cnt2] = q[i];}}else {long long pre_sum = q[i].pre_sum, pre_num = q[i].pre_num;long long cur_sum = sum.query(q[i].r) - sum.query(q[i].l - 1), cur_num = num.query(q[i].r) - num.query(q[i].l - 1);if ((q[i].r - q[i].l + 1) - pre_num - cur_num + (pre_sum + cur_sum) / (mid + 1) >= q[i].k) {q[i].pre_sum += cur_sum, q[i].pre_num += cur_num;qr[++cnt2] = q[i];}else {ql[++cnt1] = q[i];}}}for (int i = 1; i <= cnt1; i++) {if (ql[i].op) {sum.update(ql[i].l, -ql[i].r * ql[i].op), num.update(ql[i].l, -ql[i].op);}}for (int i = 1; i <= cnt1; i++) {q[L + i - 1] = ql[i];}for (int i = 1; i <= cnt2; i++) {q[L + cnt1 + i - 1] = qr[i];}solve(L, L + cnt1 - 1, l, mid);solve(L + cnt1, R, mid + 1, r);
}int main() {// freopen("in.txt", "r", stdin);// freopen("out.txt", "w", stdout);scanf("%d %d %d", &n, &m, &T);for (int i = 1; i <= n; i++) {scanf("%d", &a[i]);a[i] = m - a[i] - 1;q[++cnt] = {1, 0, i, a[i], 0, 0, 0};}for (int i = 1, op; i <= T; i++) {ans[i] = -1;scanf("%d", &op);if (op & 1) {int l, r, k;scanf("%d %d %d", &l, &r, &k);q[++cnt] = {0, i, l, r, r - l + 1 - k, 0, 0};}else {int u, v;scanf("%d %d", &u, &v);q[++cnt] = {-1, 0, u, a[u], 0, 0, 0};a[u] = m - v - 1;q[++cnt] = {1, 0, u, a[u], 0, 0, 0};}}solve(1, cnt, 0, 100000000000000);for (int i = 1; i <= T; i++) {if (ans[i] != -1) {printf("%lld\n", ans[i]);}}return 0;
}