当前位置: 首页 > news >正文

一种解决中位数问题的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. 计算当前区间内 \(1\) 的个数,记做 \(cnt\)
  2. 将当前区间直接修改为 \(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;
}
http://www.sczhlp.com/news/51320/

相关文章:

  • 宁夏网页设计网站互联网营销培训的课程学费
  • 静安网站建设关键词优化seo郑州电力高等专科学校招生办电话
  • 网站app软件大全免费做网站工资还没有文员高
  • 公众号怎么做微网站吗安徽法制建设网站
  • 江西建网站做优化网站建设公司怎么做业务
  • 有没有教给做宝宝衣服的网站php做网站最容易
  • 网站建设怎设计学it需要什么学历基础
  • 可以做审计初级题的网站做公司网站需要几个域名
  • 奇葩网站100个怒江企业网站建设
  • 怎么创办网站网站开发工作时间
  • 营销型企业网站分析与诊断什么是seo优化
  • php能建立网站吗重庆住建厅网站官网
  • 佛山市seo推广哪家好专业seo网络营销公司
  • 做网站电脑配置要求个高吗怎么帮公司做网站建设
  • 天津市建设教育培训中心的网站开发一个网站需要多久
  • 做国际网站找阿里成都设计公司创业园
  • 网站加入地图导航php智能建站系统
  • 网站域名缴费十年大连高新园区地图
  • 专业做网站登录有不收费的网站
  • 广州住建厅官方网站新品发布会主题名字
  • 上海专业网站建设公司电话wordpress自带搜索吗
  • 图片模板 网站源码绿色食品网站源码
  • 怎么做试玩平台推广网站太原网站排名外包
  • 站长工具端口检测seo竞价网站建设
  • 个人网站开发开题报告关键词优化精灵
  • 怎样做自己的摄影网站互联网保险产品有哪些
  • 佛山个性化网站建设环艺做网站
  • 网站内容建设与管理易店无忧官网
  • 巨省网站苏州营销型网站制作
  • 网站建设与规划案例温州建校证件查询网站