一种解决中位数问题的trick
前言
最近在 vp 的过程中,遇到了与中位数有关的问题。看了题解之后了解到这种问题存在相关的套路。因此在这里做出总结。
做法
想要找到数组中某个子数组的中位数,或者只是单纯的找到某个特定的数,我们可以将数组转化为01串或者正负串,即将大于等于需求的数字标记为 \(1\) ,否则标记为 \(0\) 或者 \(-1\) ,此时判断这个数是否满足我们的要求只需要看当前子数组中 \(1\) 的数量。
the Median of the Median of the Median
出自 2023 年 icpc 网络赛第二场。
思路
二分要找的中位数,将大于等于当前数字的数设为 \(1\) ,否则设为 \(-1\) ,如果当前的数字是中位数,显然这个串的和应该为 \(0\) 。因此用二维前缀和维护每个子数组的和,如果最后下来所有子数组的总和大于等于 \(0\) ,缩小右端点,否则移动左端点。
代码
int s1[N][N];
int s[N], b[N][N];
void solve(void) {
int n; std::cin >> n;
std::vector<i64> a(n + 1);
for(int i = 1; i <= n; i++) std::cin >> a[i];
i64 l = 0, r = *std::max_element(a.begin(), a.end()), ans = 0;
auto check = [&](i64 x) -> bool {
s[0] = 0;
for(int i = 1; i <= n; i++) {
int num = (a[i] <= x ? 1 : -1);
s[i] = s[i - 1] + num;
}
for(int i = 1; i <= n; i++) {
for(int j = i; j <= n; j++) {
b[i][j] = (s[j] - s[i - 1] >= 0 ? 1 : - 1);
}
}
for(int i = 1; i <= n; i++)
for(int j = 1; j <= n; j++)
s1[i][j] = s1[i - 1][j] + s1[i][j - 1] - s1[i - 1][j - 1] + b[i][j];
int sum = 0;
for(int i = 1; i <= n; i++) {
for(int j = i; j <= n; j++) {
int c = (s1[j][j] - s1[i - 1][j] - s1[j][i - 1] + s1[i - 1][i - 1] >= 0 ? 1 : -1);
sum += c;
}
}
return sum >= 0;
};
while(l <= r) {
i64 mid = (l + r) / 2;
if(check(mid)) {
r = mid - 1;
ans = mid;
} else l = mid + 1;
}
std::cout << ans;
}
[HEOI2016/TJOI2016] 排序
出自 2016 年河北省选。
思路
直接排序显然会超时。考虑二分最后的结果,将大于等于当前的数设为 \(1\) ,否则为 \(0\) 。此时我们就可以把排序转化为下面的过程:
- 计算当前区间内 \(1\) 的个数,记做 \(cnt\) 。
- 将当前区间直接修改为 \(len - cnt\) 个 \(0\) 放前面, \(cnt\) 个 \(1\) 放后面。
降序排序只需要改变 01 的顺序就可以了。
最后我们只需要判断第 \(q\) 个位置的数是否是 \(1\) ,如果是 \(1\) ,说明当前位置小于等于要找的答案,移动左端点,否则移动右端点。容易发现上面的过程是具有单调性的。
考虑如何维护查找和修改数组,可以使用 RMQ ,这里使用了线段树。
代码
struct SegTree {
int n;
std::vector<int> sum, tag;
SegTree(int n_): n(n_) {
sum.assign(4 * n, 0);
tag.assign(4 * n, -1);
}
void pull(int o) {
sum[o] = sum[o * 2] + sum[o * 2 + 1];
}
void apply(int o, int l, int r, int v) {
sum[o] = v * (r - l + 1);
tag[o] = v;
}
void push(int o, int l, int r) {
if(tag[o] != -1) {
int m = (l + r) / 2;
apply(o * 2, l, m, tag[o]);
apply(o * 2 + 1, m + 1, r, tag[o]);
tag[o] = -1;
}
}
void build(int o, int l, int r, const std::vector<int> &a) {
tag[o] = -1;
if(l == r) {
sum[o] = a[l];
return;
}
int m = (l + r) / 2;
build(o * 2, l, m, a);
build(o * 2 + 1, m + 1, r, a);
pull(o);
}
void build(const std::vector<int> &a) {
build(1, 1, n, a);
}
int query(int o, int l, int r, int L, int R) {
if(R < l || r < L) return 0;
if(L <= l && r <= R) return sum[o];
push(o, l, r);
int m = (l + r) / 2;
return query(o * 2, l, m, L, R) + query(o * 2 + 1, m + 1, r, L, R);
}
int query_sum(int x, int y) {
return query(1, 1, n, x, y);
}
void update(int o, int l, int r, int L, int R, int v) {
if(R < l || r < L) return;
if(L <= l && r <= R) {
apply(o, l, r, v);
return;
}
push(o, l , r);
int m = (l + r) / 2;
update(o * 2, l, m, L, R, v);
update(o * 2 + 1, m + 1, r, L, R, v);
pull(o);
}
void update(int x, int y, int v) {
update(1, 1, n, x, y, v);
}
int query(int o, int l, int r, int p) {
if(l == r) return sum[o];
push(o, l, r);
int m = (l + r) / 2;
if(p <= m) return query(o * 2, l, m, p);
return query(o * 2 + 1, m + 1, r, p);
}
int query_point(int p) {
return query(1, 1, n, p);
}
};
void solve(void) {
int n, m, q; std::cin >> n >> m;
std::vector<int> a(n + 1);
std::vector<int> op(m + 1), L(m + 1), R(m + 1);
for(int i = 1; i <= n; i++) {
std::cin >> a[i];
}
for(int i = 1; i <= m; i++) std::cin >> op[i] >> L[i] >> R[i];
std::cin >> q;
SegTree seg(n);
auto check = [&](int x) -> bool {
std::vector<int> b(n + 1);
for(int i = 1; i <= n; i++) {
if(a[i] >= x) b[i] = 1;
else b[i] = 0;
}
seg.build(b);
for(int i = 1; i <= m; i++) {
int cnt = seg.query_sum(L[i], R[i]);
if(op[i] == 0) {
seg.update(L[i], R[i] - cnt, 0);
seg.update(R[i] - cnt + 1, R[i], 1);
} else {
seg.update(L[i], L[i] + cnt - 1, 1);
seg.update(L[i] + cnt, R[i], 0);
}
}
return seg.query_point(q);
};
int l = 1, r = n, ans = 1;
while(l <= r) {
int mid = (l + r) / 2;
if(check(mid)) {
ans = mid;
l = mid + 1;
} else r = mid - 1;
}
std::cout << ans;
}
Range Sort Query
出自 AtCoder-abc237
思路
类似地,可以用线段树维护排序操作。将所有的操作做两遍,第一次的时候将大于等于 \(x\) 的设为 \(1\) ,否则设为 \(0\) ;第二次的时候将大于 \(x\) 的设为 \(1\) ,否则设为 \(0\) ;可以发现两次操作造成的结果仅有一个位置不同,这个位置就是 \(p_i = x\) 的位置。因此需要保存下来对两次操作对数组的修改,方便最后比对。
代码
// 线段树代码同上
void solve(void) {
int n, m, q; std::cin >> n >> m >> q;
std::vector<int> a(n + 1);
std::vector<int> op(m + 1), L(m + 1), R(m + 1);
for(int i = 1; i <= n; i++) {
std::cin >> a[i];
}
for(int i = 1; i <= m; i++) std::cin >> op[i] >> L[i] >> R[i];
SegTree seg(n);
std::vector<int> b(n + 1);
for(int i = 1; i <= n; i++) b[i] = a[i] >= q ? 1 : 0;
auto work = [&](std::vector<int> &x) -> void {
for(int i = 1; i <= m; i++) {
int cnt = seg.query_sum(L[i], R[i]);
if(op[i] == 1) {
seg.update(L[i], R[i] - cnt, 0);
seg.update(R[i] - cnt + 1, R[i], 1);
} else {
seg.update(L[i], L[i] + cnt - 1, 1);
seg.update(L[i] + cnt, R[i], 0);
}
}
for(int i = 1; i <= n; i++) x[i] = seg.query_point(i);
};
seg.build(b);
work(b);
std::vector<int> c(n + 1);
for(int i = 1; i <= n; i++) c[i] = a[i] > q ? 1 : 0;
seg.build(c);
work(c);
for(int i = 1; i <= n; i++) {
if(b[i] != c[i]) {
std::cout << i << "\n";
return;
}
}
}
Median Pyramid Hard
出自 AtCoder-agc006
思路
观察样例,将大于等于最顶端的数设为 \(1\) ,否则设为 \(0\) ,可以发现对于这个 01 金字塔,每个格子(除了最底层的)的数字取决于它下面的三个格子中 01 的数量,当 \(1\) 的数量大于 \(0\) 时,当前格子一定为 \(1\) ;否则为 \(0\) 。
因此可以二分答案,每次根据二分出来的结果将输入的 \(2 \times n - 1\) 个数修改为 01 串。但是直接拿最底层往上递推时间复杂度会达到 \(O(n^2)\) 。进一步观察,会发现最上面的格子是什么取决于最底层中离中心点最近的是两个连续的 \(1\) 还是 \(0\) ,因此只需要从重点出发,每次找当前点往左或者往右是否存在两个连续的 \(1\) 或者 \(0\) 就可以判断出答案了。
最后考虑一个特殊情况,即最底层转化为了
a b a b a b a b ...
这种交替的形式,此时最顶层的结果取决于最左边的数字。
代码
void solve(void) {
int n; std::cin >> n;
std::vector<int> a(n * 2);
for(int i = 1; i <= n * 2 - 1; i++) std::cin >> a[i];
int l = 1, r = 2 * n - 1, ans = 0;
auto check = [&](int x) -> bool{
for(int i = 0; i < n - 1; i++) {
if((a[n + i] > x && a[n + i + 1] > x) || (a[n - i] > x && a[n - i - 1] > x)) return false;
if((a[n + i] <= x && a[n + i + 1] <= x ) || (a[n - i] <= x && a[n - i - 1] <= x)) return true;
}
return a[1] <= x;
};
while(l <= r) {
int mid = (l + r) / 2;
if(check(mid)) {
r = mid - 1;
ans = mid;
} else l = mid + 1;
}
std::cout << ans;
}

浙公网安备 33010602011771号