题目描述
给定一个长度为 \(N\) 的序列 \(A\),以及 \(Q\) 次询问,每次询问给定一个 \(x\)。
你可以执行以下操作任意次:
- 选择一个 \(1\le i \le N\) 使得 \(A_i \ge x\)。
- 令 \(A_i \leftarrow A_i - x\)。
求 \(A\) 的最小中位数。
这里中位数是 \(A\) 排序后的第 \(\lfloor \frac{n}{2}\rfloor+1\) 个元素。
思路
很显然,令一个 \(A_i-x\) 一定不会使答案更劣,所以肯定会把所有操作进行到底,也就是 \(A_i\leftarrow A_i \bmod x\)。然后让你求这种情况下的中位数。
首先对 \(A_i\) 的值做一个前缀和。对于每个 \(x\),二分其中位数。
二分的 check 也很简单,直接枚举 \(x\) 的倍数 \(y\),求 \(y\) 到 \(y+mid\) 有多少个数,如果总数 \(\ge \lfloor \frac{n}{2}\rfloor+1\) 则合法,否则不合法。
这样的时间复杂度是 \(O(N\log^2 N)\),因为这里面是一个调和级数,所以时间不会炸。
空间复杂度 \(O(N)\),时间复杂度 \(O(N \log^2 N + Q)\)。
代码
#include<bits/stdc++.h>
using namespace std;const int MAXN = 100005;int T, n, q, a[MAXN], sum[MAXN], ans[MAXN];bool check(int x, int y) {int res = 0;for(int i = 0; i * x <= n; ++i) {res += sum[min(n, i * x + y)] - (i * x - 1 >= 0 ? sum[i * x - 1] : 0);}return res >= n / 2 + 1;
}int Binary_Search(int x) {int l = 0, r = x - 1;for(; l < r; ) {int mid = (l + r) >> 1;(check(x, mid) ? r = mid : l = mid + 1);}return l;
}void Solve() {cin >> n >> q;for(int i = 1; i <= n; ++i) {sum[i] = 0;}for(int i = 1; i <= n; ++i) {cin >> a[i];sum[a[i]]++;}for(int i = 1; i <= n; ++i) {sum[i] += sum[i - 1];}for(int i = 1; i <= n; ++i) {ans[i] = Binary_Search(i);}for(int i = 1, x; i <= q; ++i) {cin >> x;cout << ans[x] << " \n"[i == q];}
}int main() {ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);for(cin >> T; T--; Solve()) {}return 0;
}