该内容需要有一定的数据结构基础,前置知识:二维前缀和、树状数组、线段树、扫描线等
二维数点的解法较多,可自行查找和学习其他解法

二维数点简介

二维数点又称二维偏序,它是这样一类问题,给出一个二维平面內的若干个点,多次询问某个矩形区域內包含多少个点(边界也算)。又或者,给一个长为 n n n 的序列,多次询问区间 [ l , r ] [l,r] [l,r] 中值在 [ x , y ] [x,y] [x,y] 内的元素个数。


能解决这一问题的数据结构较多,包括树状数组/线段树、K-D Tree、可持久化线段树等,运用CDQ分治可解决更高维的偏序问题。以下着重讲解扫描线思想+树状数组解决二维偏序问题的方法。

二维数点的本质,是我们要查询一个矩形区域內的点数,首先考虑二维前缀和,对于矩形 ( x 1 , y 1 ) , ( x 2 , y 2 ) (x_1,y_1),(x_2,y_2) (x1​,y1​),(x2​,y2​),其二维前缀和为 s [ x 2 ] [ y 2 ] − s [ x 1 − 1 ] [ y 2 ] − s [ x 2 ] [ y 1 − 1 ] + s [ x 1 − 1 ] [ y 1 − 1 ] s[x_2][y_2]-s[x_1-1][y_2]-s[x_2][y_1-1]+s[x_1-1][y_1-1] s[x2​][y2​]−s[x1​−1][y2​]−s[x2​][y1​−1]+s[x1​−1][y1​−1]。可惜由于时空的双重限制,我们几乎不可能求出二维前缀和。

假设我们将矩形按横坐标排序,对于当前 x x x,如果所有 x ′ ≤ x x' \leq x x′≤x 的点的纵坐标都已知,那么将会有 s [ x ] [ y ] = q u e r y ( 1 , y ) s[x][y]=query(1,y) s[x][y]=query(1,y),其中 q u e r y ( 1 , y ) query(1,y) query(1,y) 代表查询纵坐标在 [ 1 , y ] [1,y] [1,y] 内的点数,这意味着我们把二维前缀和压成了一维的,每次查询只和 y y y 有关,这可以用简单数据结构来维护。整个思想与线段树的扫描线算法是十分相似的。

那么如何求出一个矩形区域內的答案呢?根据二维前缀和公式,我们只需要分别求出其四个区域的贡献(或正或负),相加即可。于是我们在从左往右沿 x x x 轴前进的过程中,不断将 x ′ ≤ x x' \leq x x′≤x 的点的纵坐标加入树状数组,依次求出所有询问对应的四个区域的贡献。询问需要提前离线,每个询问拆为四个区域。

于是,整个算法过程就是

  • 将所有点按横坐标排序
  • 将所有矩形询问拆成四个区域,即四次询问,所有询问按 x x x 轴排序
  • 遍历询问,设当前横坐标为 x x x,保证 x ′ ≤ x x' \leq x x′≤x 的所有点的纵坐标已加入树状数组,在树状数组中查询答案,贡献加至原询问处
  • 输出每个原询问的答案

练习

[SHOI2007]园丁的烦恼

题目链接

题意:平面上给出 n n n 个点, m m m 次询问,每次给出一个矩形,询问处于所给矩形区域內的点的数量。

思路:模板题,注意到 n , m n,m n,m 数据范围较大,离散化带个log容易被卡,而坐标范围为 [ 0 , 1 0 7 ] [0,10^7] [0,107],其实可以不进行离散化,但是由于坐标范围到0,树状数组不支持0下标,不妨将坐标全部加1。

参考代码:

#include <bits/stdc++.h>
using namespace std;constexpr int MAXN = 1e7 + 5;
int sum[MAXN], ans[MAXN];
vector<pair<int, int>> vec;
vector<tuple<int, int, int, int>> q;inline int lowbit(int x) { return x & (-x); }void add(int pos, int x)
{for (; pos < MAXN; pos += lowbit(pos))sum[pos] += x;
}int query_presum(int pos)
{int ans = 0;for (; pos > 0; pos -= lowbit(pos))ans += sum[pos];return ans;
}int main()
{ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);int n, m, x1, x2, y1, y2;cin >> n >> m;for (int i = 0; i < n; i++){cin >> x1 >> y1, ++x1, ++y1;vec.emplace_back(x1, y1);}sort(vec.begin(), vec.end());for (int i = 0; i < m; i++){cin >> x1 >> y1 >> x2 >> y2;++x1, ++y1, ++x2, ++y2;q.emplace_back(x1 - 1, y1 - 1, 1, i);q.emplace_back(x1 - 1, y2, -1, i);q.emplace_back(x2, y1 - 1, -1, i);q.emplace_back(x2, y2, 1, i);}sort(q.begin(), q.end());int cur = 0;for (auto [x, y, c, id] : q){while (cur < n && vec[cur].first <= x)add(vec[cur].second, 1), ++cur;ans[id] += c * query_presum(y);}for (int i = 0; i < m; i++)cout << ans[i] << "\n";return 0;
}

[CQOI2017]老C的任务

题目链接

题意:平面上给出 n n n 个点,每个点有权值 p i p_i pi​, m m m 次询问,每次给出一个矩形,询问处于所给矩形区域內的点的权值之和。

思路:我们只会维护点的数量,如果还要维护每个点对应的权值,这是做不到的。但是,把权值 p i p_i pi​ 看作有 p i p_i pi​ 个点在当前位置上重合,显然这个问题就转换成了例题。本题中需要离散化。

参考代码:

#include <bits/stdc++.h>
using namespace std;constexpr int MAXN = 3e5 + 5;
int64_t sum[MAXN], ans[MAXN];
vector<int> yy;
vector<tuple<int, int, int>> vec;
vector<tuple<int, int, int, int>> q;inline int lowbit(int x) { return x & (-x); }void add(int pos, int64_t x)
{for (; pos < MAXN; pos += lowbit(pos))sum[pos] += x;
}int64_t query_presum(int pos)
{int64_t ans = 0;for (; pos > 0; pos -= lowbit(pos))ans += sum[pos];return ans;
}int main()
{ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);int n, m, x1, x2, y1, y2, p;cin >> n >> m;for (int i = 0; i < n; i++){cin >> x1 >> y1 >> p;yy.push_back(y1);vec.emplace_back(x1, y1, p);}sort(vec.begin(), vec.end());for (int i = 0; i < m; i++){cin >> x1 >> y1 >> x2 >> y2;yy.push_back(y1 - 1), yy.push_back(y2);q.emplace_back(x1 - 1, y1 - 1, 1, i);q.emplace_back(x1 - 1, y2, -1, i);q.emplace_back(x2, y1 - 1, -1, i);q.emplace_back(x2, y2, 1, i);}sort(q.begin(), q.end());sort(yy.begin(), yy.end());yy.erase(unique(yy.begin(), yy.end()), yy.end());int cur = 0;for (auto [x, y, c, id] : q){y = lower_bound(yy.begin(), yy.end(), y) - yy.begin() + 1;while (cur < n){auto [_x, _y, p] = vec[cur];if (_x > x)break;_y = lower_bound(yy.begin(), yy.end(), _y) - yy.begin() + 1;add(_y, p), ++cur;}ans[id] += c * query_presum(y);}for (int i = 0; i < m; i++)cout << ans[i] << "\n";return 0;
}

[SDOI2009]HH的项链

题目链接

题意:给一个长为 n n n 的序列,每个位置元素值为 a i a_i ai​,多次询问区间 [ l , r ] [l,r] [l,r] 中元素种类数。

思路:照常把 x x x 所在一维降掉后,发现 y y y 轴并没有明显的偏序关系。可以这样考虑,我们只计每个元素第一次在区间中出现时有贡献,设 p r e [ i ] pre[i] pre[i] 表示位置 i i i 的元素前一次出现的位置,在整个序列中第一次出现时记为0,由此,总的二维偏序关系为:
{ l ≤ x ≤ r p r e [ x ] < l \left\{ \begin{aligned} l \leq x & \leq r \\ pre[x] & < l \\ \end{aligned} \right. {l≤xpre[x]​≤r<l​


由于树状数组不支持0下标,可以稍作平移。能够看到在设定的偏序关系下,查询返回了正确的结果。

参考代码:

#include <bits/stdc++.h>
using namespace std;constexpr int MAXN = 1e6 + 5;
int sum[MAXN], pre[MAXN], ans[MAXN];
vector<pair<int, int>> vec;
vector<tuple<int, int, int, int>> q;inline int lowbit(int x) { return x & (-x); }void add(int pos, int x)
{for (; pos < MAXN; pos += lowbit(pos))sum[pos] += x;
}int query_presum(int pos)
{int ans = 0;for (; pos > 0; pos -= lowbit(pos))ans += sum[pos];return ans;
}int main()
{ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);int n, m, a, l, r;cin >> n;for (int i = 3; i <= n + 2; i++){cin >> a;vec.emplace_back(i, pre[a] ? pre[a] : 2), pre[a] = i;}sort(vec.begin(), vec.end());cin >> m;for (int i = 0; i < m; i++){cin >> l >> r, l += 2, r += 2;q.emplace_back(l - 1, 1, 1, i);q.emplace_back(l - 1, l - 1, -1, i);q.emplace_back(r, 1, -1, i);q.emplace_back(r, l - 1, 1, i);}sort(q.begin(), q.end());int cur = 0;for (auto [x, y, c, id] : q){while (cur < n && vec[cur].first <= x)add(vec[cur].second, 1), ++cur;ans[id] += c * query_presum(y);}for (int i = 0; i < m; i++)cout << ans[i] << "\n";return 0;
}

CF1320C - World of Darkraft: Battle for Azathoth

题目链接

题意:你有 n n n 件武器, m m m 件防具,每件装备有对应的攻击力/防御力和价格,你必须各选择一件。有 p p p 只怪物,每只防御力、攻击力、掉落金币分别为 x i , y i , z i x_i,y_i,z_i xi​,yi​,zi​,假设你攻击力和防御力分别是 a k , b k a_k,b_k ak​,bk​,你只能战胜 a k > x i a_k>x_i ak​>xi​ 且 b k > y i b_k>y_i bk​>yi​ 的怪物。求最大收益(可能为负)。

思路:题目已经给出了二维偏序关系,设 x x x 轴和 y y y 轴分别表示攻击力和防御力,以怪物作为点,可以发现询问是武器和防具任意组合后,以 ( 0 , 0 ) , ( b k − 1 , a k − 1 ) (0,0),(b_k-1,a_k-1) (0,0),(bk​−1,ak​−1) 为顶点的一个矩形。枚举武器降掉一维,逐个加点,问题变成每次对于所有防具,求最大收益。
这需要使用线段树来维护,先设定 [ 1 , 1 0 6 ] [1,10^6] [1,106] 內所有防具的初始收益,每次加点相当于给 [ y i + 1 , 1 0 6 ] [y_i+1,10^6] [yi​+1,106] 内的所有防具增加 z i z_i zi​ 的收益,查询全局最值并更新答案即可。

参考代码:

#include <bits/stdc++.h>
using namespace std;constexpr int MAXN = 1e6 + 5, N = 1e6 + 1;
int mx[MAXN << 2], add[MAXN << 2], w[MAXN];
vector<pair<int, int>> weapon;
vector<tuple<int, int, int>> mon;void pushDown(int rt)
{mx[rt << 1] += add[rt], mx[rt << 1 | 1] += add[rt];add[rt << 1] += add[rt], add[rt << 1 | 1] += add[rt];add[rt] = 0;
}void modify(int L, int R, int C, int l, int r, int rt)
{if (L <= l && r <= R){mx[rt] += C, add[rt] += C;return;}int mid = (l + r) >> 1;pushDown(rt);if (L <= mid)modify(L, R, C, l, mid, rt << 1);if (R > mid)modify(L, R, C, mid + 1, r, rt << 1 | 1);mx[rt] = max(mx[rt << 1], mx[rt << 1 | 1]);
}int query(int L, int R, int l, int r, int rt)
{if (L <= l && r <= R)return mx[rt];int mid = (l + r) >> 1;pushDown(rt);int ans = -2e9;if (L <= mid)ans = max(ans, query(L, R, l, mid, rt << 1));if (R > mid)ans = max(ans, query(L, R, mid + 1, r, rt << 1 | 1));return ans;
}int main()
{ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);int n, m, p, x, y, z;cin >> n >> m >> p;for (int i = 1; i <= n; i++)cin >> x >> y, weapon.emplace_back(x, y);fill(w, w + MAXN, -2e9);for (int i = 1; i <= m; i++)cin >> x >> y, w[x] = max(w[x], -y);for (int i = 1; i <= N; i++)modify(i, i, w[i], 1, N, 1);for (int i = 1; i <= p; i++)cin >> x >> y >> z, mon.emplace_back(x, y, z); // <def, atk, gold>sort(weapon.begin(), weapon.end());sort(mon.begin(), mon.end());int cur = 0, ans = -2e9;for (auto [a, ca] : weapon){while (cur < mon.size()){auto [x, y, z] = mon[cur];if (x >= a)break;modify(y + 1, N, z, 1, N, 1), ++cur;}ans = max(ans, -ca + query(1, N, 1, N, 1));}cout << ans << endl;return 0;
}

[传智杯 #4 初赛] 小卡与落叶

题目链接

题意:给出一棵树,初始所有结点为绿色。支持两种操作,一是把整棵树染绿,再将深度 ≥ x \geq x ≥x 的结点染黄,二是询问以结点 x x x 为根的子树中黄色结点数量。

思路:一个重要的观察是,对于每次询问,显然只需要考虑它前面的最后一次操作。我们知道子树的dfs序是一个连续区间,设 L [ v ] , R [ v ] L[v],R[v] L[v],R[v] 分别代表子树根结点 v v v 的dfs序及其子树中最大dfs序,那么二维偏序关系为 L [ v ] ≤ d f n [ u ] ≤ R [ v ] L[v] \leq dfn[u] \leq R[v] L[v]≤dfn[u]≤R[v] 且 d e p [ u ] ≥ x dep[u] \geq x dep[u]≥x。这是一个把二维数点搬到树上的题目,以dfs序为横轴,深度为纵轴,显然要降掉dfs序,问题变为每次查询符合深度要求的点数。直接顺序读取操作和询问,添加矩形查询,转变为例题。

参考代码:

#include <bits/stdc++.h>
using namespace std;constexpr int MAXN = 1e5 + 5;
int L[MAXN], R[MAXN], rnk[MAXN], dep[MAXN], dfn;
int sum[MAXN], ans[MAXN];
vector<int> G[MAXN];
vector<tuple<int, int, int, int>> q;inline int lowbit(int x) { return x & (-x); }void add(int pos, int x)
{for (; pos < MAXN; pos += lowbit(pos))sum[pos] += x;
}int query_presum(int pos)
{int ans = 0;for (; pos > 0; pos -= lowbit(pos))ans += sum[pos];return ans;
}void dfs(int v, int fa)
{L[v] = ++dfn;rnk[dfn] = v;dep[v] = dep[fa] + 1;for (auto u : G[v]){if (u != fa)dfs(u, v);}R[v] = dfn;
}int main()
{ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);int n, m;cin >> n >> m;for (int i = 1, x, y; i < n; i++){cin >> x >> y;G[x].push_back(y), G[y].push_back(x);}dfs(1, 0);int lst = 1e5 + 2, cnt = 0;for (int i = 1, op, x; i <= m; i++){cin >> op >> x;if (op == 1)lst = x;else{++cnt;q.emplace_back(L[x] - 1, lst - 1, 1, cnt);q.emplace_back(L[x] - 1, 1e5 + 4, -1, cnt);q.emplace_back(R[x], lst - 1, -1, cnt);q.emplace_back(R[x], 1e5 + 4, 1, cnt);}}sort(q.begin(), q.end());int cur = 1;for (auto [x, y, c, id] : q){while (cur <= n && cur <= x)add(dep[rnk[cur]], 1), ++cur;ans[id] += c * query_presum(y);}for (int i = 1; i <= cnt; i++)cout << ans[i] << "\n";return 0;
}

【数据结构】二维数点/二维偏序相关推荐

  1. 【无码专区8】三角形二维数点——计数有多少个给定点落在三角形区域内

    因为只有std,没有自我实现,所以是无码专区 主要是为了训练思维能力 solution才是dls正解,但是因为只有潦草几句,所以大部分会有我自己基于正解上面的算法实现过程,可能选择的算法跟std中dl ...

  2. F. Paper Grading(Trie树+dfs序+二维数点)

    F. Paper Grading 大佬题解 一般关于前缀的问题基本都是Trie树. 首先将所给字符串建立一棵Trie树,Trie能够解决一个字符串在一个字符串集合中出现的次数,而查询前缀次数只需要找到 ...

  3. 点分治 + 树状数组 ---- E. Close Vertices(点分治 + 二维数点)

    题目链接 题目大意: 给出一棵树,问有多少条路径权值和不大于www,长度不大于lll 解题思路: 首先树上路径问题大概率就是点分治了 但是我们对于每个路径有两个性质就是(li,wi)(l_i,w_i) ...

  4. 洛谷 - P3899 [湖南集训]谈笑风生(dfs序+主席树/二维数点)

    题目链接:点击查看 题目大意:设 TTT 为一棵有根树,我们做如下的定义: 设 aaa 和 bbb 为 TTT 中的两个不同节点.如果 aaa 是 bbb 的祖先,那么称"aaa 比 bbb ...

  5. 洛谷 - P2163 [SHOI2007]园丁的烦恼(不带修二维数点-树状数组/主席树)

    题目链接:点击查看 题目大意:二维平面坐标系中给出 nnn 个坐标点,然后是 mmm 次询问,每次询问需要回答一个闭合矩阵中有多少个点 题目分析:想挂树套树来着,但是复杂度有点大.本题不带修且可以离线 ...

  6. P3899 [湖南集训]谈笑风生 主席树解决二维数点

    传送门 文章目录 题意: 思路: 题意: 思路: 由于a,ba,ba,b都比ccc厉害,那么a,ba,ba,b一定是某个是某个的祖先.那么就分为两种情况了: (1)(1)(1) bbb在aaa上面,约 ...

  7. LG P4899 [IOI2018] werewolf 狼人(kruskal重构树,二维数点)

    LG P4899 [IOI2018] werewolf 狼人 Solution 我们发现010101限制长这样子: ∃x(minids−>x≥L&maxidx−>e≤R)→1\ex ...

  8. P2163 [SHOI2007]园丁的烦恼(二维数点模板题)

    P2163 [SHOI2007]园丁的烦恼 题意: 在一个二维平面内有一些点,给你一个左上角和右下角的点,问这个范围内有多少点 题解: 二维数点模板题 我们设F(a,b)表示以(0,0)为左下角,(a ...

  9. P3899 [湖南集训]更为厉害(线段树合并、长链剖分、二维数点)

    P3899 [湖南集训]更为厉害 若 deepb<deepa\text{deep}_b<\text{deep}_adeepb​<deepa​:c 在点 a 的子树中,根据乘法原理计算 ...

最新文章

  1. 理解卷积神经网络中的自注意力机制
  2. 全文搜索引擎 Elasticsearch 简介 及其与 Python 的对接实现
  3. 爬楼梯(Leetcode)
  4. LeetCode 890. 查找和替换模式(哈希表)
  5. NuGet version
  6. Portainer简介及部署
  7. 二级省市联动下拉菜单
  8. 用html代码实现做题记分,html解析cricinfo记分卡
  9. Spring-IoC注解
  10. svchost.exe 上传下载占用大量资源
  11. 关于javaBean运行后出现Name was not previously introduced as per JSP.5.3的解决方法
  12. 蓝桥杯摔手机测试次数
  13. 线上盲盒电商模式运营
  14. 《江苏省ITS体系框架与规划——需求分析子课题》工作大纲评审会在南京举行[转贴,出处:ITSC 作者:刘浩,张可]
  15. 个人博客配置SSL安全文件
  16. 你必须知道的家庭急救常识
  17. 判断邮箱的正则表达式
  18. 彻底删除MySQL57服务
  19. 选择高光阴影中间调的方法
  20. 关于 在国产麒麟系统上使用QProcess配合管道命令执行shell命令获取预期结果输出失败 的解决方法

热门文章

  1. 前端输入框校验限制不能输入中文
  2. 修改linux资源限制参数命令,Linux的资源限制功能cgroups v1和cgroups v2的详细介绍
  3. JSP网页背景图片自适应
  4. 详细剖解Levenshtein距离算法(附python实现)
  5. 【网络】RDMA技术简述
  6. c语言localtime_s用法,localtime、localtime_s、localtime_r的使用
  7. 实现div跟随鼠标移动、点击、拖动而产生的变化
  8. brackets编写java,Brackets - 一款免费的前端开发工具
  9. 【转载声明】转载本博客的文章请注明原始出处和作者
  10. 课程设计--图书信息管理系统(C语言)