【数据结构】二维数点/二维偏序

这篇具有很好参考价值的文章主要介绍了【数据结构】二维数点/二维偏序。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

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

二维数点简介

二维数点又称二维偏序,它是这样一类问题,给出一个二维平面內的若干个点,多次询问某个矩形区域內包含多少个点(边界也算)。又或者,给一个长为 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[x11][y2]s[x2][y11]+s[x11][y11]。可惜由于时空的双重限制,我们几乎不可能求出二维前缀和。

假设我们将矩形按横坐标排序,对于当前 x x x,如果所有 x ′ ≤ x x' \leq x xx 的点的纵坐标都已知,那么将会有 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 xx 的点的纵坐标加入树状数组,依次求出所有询问对应的四个区域的贡献。询问需要提前离线,每个询问拆为四个区域。

于是,整个算法过程就是

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

练习

[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。

参考代码:文章来源地址https://www.toymoban.com/news/detail-609803.html

#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. {lxpre[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),(bk1,ak1) 为顶点的一个矩形。枚举武器降掉一维,逐个加点,问题变成每次对于所有防具,求最大收益。
这需要使用线段树来维护,先设定 [ 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;
}

到了这里,关于【数据结构】二维数点/二维偏序的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 数据结构二维数组计算题,以行为主?以列为主?

    1.假设以行序为主序存储二维数组A=array[1..100,1..100],设每个数据元素占2个存储单元,基地址为10,则LOC[5,5]=(  )。 A.808            B . 818             C.1010             D.1020 答案: B 解释:以行序为主,则 LOC[5,5]=[ ( 5-1 ) *100+ ( 5-1 ) ]*2+10=818 。 2

    2024年02月05日
    浏览(49)
  • 【数据结构】二维数组的行优先、列优先存储问题

    今天同学问我一道感觉很基础的数据结构问题,虽然答案做对了,但是原理一直比较迷,仔细看了一下题,原来是自己把自己绕进去了。。。在此记录一下,大佬如果有更好的方法,可以在评论区留言,不定期更新。 先给出行优先和列优先的计算公式: 设数组为A[m][n]( m 行

    2024年02月10日
    浏览(55)
  • 数据结构 | 寻找二维数组的最大值和对应下标 | C语言代码

    题目:         本题目要求读入M(最大为10)行N(最大为15)列个元素,找出其中最大的元素,并输出其行列值。 输入格式:         输入在第一行中给出行数m和列数n。接下来输入m*n个整数。 输出格式:         输出最大值的行号,列号,值。 输入样例: 2 3 1 2 3 4 5 6 输

    2024年02月05日
    浏览(53)
  • 算法 数据结构分类 数据结构类型介绍 数据结构线性非线性结构 算法合集 (一)

     数据结构分为:                            a.线性结构                            b.非线性结构  a.线性结构:                       数据与结构存在一对一的线性关系; a . 线性结构 存储 分为:                                   顺序存储

    2024年02月10日
    浏览(53)
  • 【算法与数据结构】--算法应用--算法和数据结构的案例研究

    一、项目管理中的算法应用 在项目管理中,算法和数据结构的应用涉及项目进度、资源分配、风险管理等方面。以下是一些案例研究,展示了算法在项目管理中的实际应用: 项目进度管理 : 甘特图算法 :甘特图是一种项目进度管理工具,它使用甘特图算法来展示项目任务

    2024年02月08日
    浏览(58)
  • 数据结构与算法设计分析—— 数据结构及常用算法

    1、顺序表与链表 线性表是 线性结构 ,是包含n个数据元素的有限序列,通过顺序存储的线性表称为 顺序表 ,它是将线性表中所有元素按照其逻辑顺序,依次存储到指定存储位置开始的一块连续的存储空间里;而通过链式存储的 链表 中,每个结点不仅包含该元素的信息,还

    2024年02月07日
    浏览(62)
  • 数据结构和算法——数据结构

    目录 线性结构  队列结构的队列 链表结构的队列 链表的面试题 单向链表应用场景 约瑟夫环问题 栈结构 中缀表达式 前缀表达式 后缀表达式 非线性结构 图 递归解决迷宫问题 递归解决八皇后问题 顺序存储方式,顺序表 常见的顺序存储结构有:数组、队列、链表、栈 链式存

    2024年02月07日
    浏览(56)
  • 数据结构与算法 --- 数据结构绪论

    早期人们都把计算机理解为数值计算工具,就是感觉计算机当然是用来计算的,所以计算机解决问题,应该是先从具体问题中抽象出一个适当的数据模型,设计出一个解此数据模型的算法,然后再编写程序,得到一个实际的软件。 可现实中,我们更多的不是解决数值计算的问

    2024年02月14日
    浏览(54)
  • 数据结构与算法——数据结构有哪些,常用数据结构详解

    数据结构是学习数据存储方式的一门学科,那么,数据存储方式有哪几种呢?下面将对数据结构的学习内容做一个简要的总结。 数据结构大致包含以下几种存储结构: 线性表,还可细分为顺序表、链表、栈和队列; 树结构,包括普通树,二叉树,线索二叉树等; 图存储结构

    2024年02月15日
    浏览(63)
  • 【数据结构与算法】不就是数据结构

      嗨喽小伙伴们你们好呀,好久不见了,我已经好久没更新博文了!之前因为实习没有时间去写博文,现在已经回归校园了。我看了本学期的课程中有数据结构这门课程(这么课程特别重要),因为之前学过一点,所以就想着深入学习一下子。毕竟这门课程对于 考研 和 就业

    2024年02月07日
    浏览(49)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包