二维偏序/数点——学习笔记

本文通过四个具体的编程题目,详细介绍了二维偏序问题的解决策略,主要涉及树状数组和线段树的数据结构。题目覆盖了离线询问、区间更新和区间最值查询等应用场景,是二维数点问题的典型应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

二维偏序/数点

  • 对于可离线的二维偏序问题一般都是先定一维的顺序,然后利用树状数组维护求解

应用

一.

  • 题目链接:https://www.luogu.com.cn/problem/P2163
  • 解题思路:二维数点的经典模板题,离线询问之后利用二维前缀和方法用树状数组维护即可

//#define LOCAL
#include <bits/stdc++.h>
using namespace std;

#define ll long long
#define mem(a, b) memset(a,b,sizeof(a))
#define sz(a) (int)a.size()
#define INF 0x3f3f3f3f
#define DNF 0x7f
#define DBG printf("this is a input\n")
#define fi first
#define se second
#define mk(a, b) make_pair(a,b)
#define pb push_back
#define LF putchar('\n')
#define SP putchar(' ')
#define p_queue priority_queue
#define CLOSE ios::sync_with_stdio(0); cin.tie(0)
#define sz(a) (int)a.size()
#define pii pair <int,int>
template<typename T>
void read(T &x) {x = 0;char ch = getchar();ll f = 1;while(!isdigit(ch)){if(ch == '-')f *= -1;ch = getchar();}while(isdigit(ch)){x = x * 10 + ch - 48; ch = getchar();}x *= f;}
template<typename T, typename... Args>
void read(T &first, Args& ... args) {read(first);read(args...);}
template<typename T>
void write(T arg) {T x = arg;if(x < 0) {putchar('-'); x =- x;}if(x > 9) {write(x / 10);}putchar(x % 10 + '0');}
template<typename T, typename ... Ts>
void write(T arg, Ts ... args) {write(arg);if(sizeof...(args) != 0) {putchar(' ');write(args ...);}}
using namespace std;

ll gcd(ll a, ll b) {
    return b == 0 ? a : gcd(b, a % b);
}

ll lcm(ll a, ll b) {
    return a / gcd(a, b) * b;
}

const int N = 5e5 + 5;
int n , m;
int tot = 0, ans[N];
struct ques {
    int x, y, id, op;
    bool operator < (const ques& no) const {
        return x < no.x;
    }
}q[N * 5];
struct point {
    int x, y;
    bool operator < (const point& no) const {
        return x < no.x;
    }
}v[N];
struct BIT
{
    int tree[10000005];
    int ed = 10000001;
    void init()
    {
        mem(tree,0) ;
    }
    int lowbit(int k)
    {
        return k & -k;
    }
    void add(int x , int k) //注意值域
    {
        while(x <= ed)
        {
            tree[x] += k ;
            x += lowbit(x) ;
        }
    }
    int sum(int x)
    {
        int ans = 0 ;
        while(x != 0)
        {
            ans += tree[x] ;
            x -= lowbit(x) ;
        }
        return ans ;
    }
    int query(int l , int r)
    {
        return sum(r) - sum(l - 1) ;
    }
} bit ;
int main()
{
    CLOSE;
    bit.init();
    cin >> n >> m;
    for (int i = 1 ; i <= n ; i ++) {
        cin >> v[i].x >> v[i].y;
        v[i].x += 1 , v[i].y += 1;
    }
    for (int i = 1 ; i <= m ; i ++)
    {
        int a, b, c, d;
        cin >> a >> b >> c >> d;
        a += 1, b += 1, c += 1, d += 1;
        q[++ tot] = {c, d, i, 1};
        q[++ tot] = {a-1, d, i, -1};
        q[++ tot] = {c, b-1, i, -1};
        q[++ tot] = {a-1, b-1, i, 1};
    }
    sort (v + 1, v + 1 + n);
    sort (q + 1, q + 1 + tot);
    int now = 1;
    for (int i = 1 ; i <= tot ; i ++)
    {
        while (v[now].x <= q[i].x && now <= n)
            bit.add(v[now].y, 1), now ++;
        ans[q[i].id] += q[i].op * bit.sum (q[i].y);
    }
    for (int i = 1 ; i <= m ; i ++)
        cout << ans[i] << endl;
}

二.

  • 题目链接: https://codeforces.com/problemset/problem/1320/C
  • 解题思路: 本题每个怪物有两个属性,并且两个属性的攻击方式均存在大小关系,所以基本可以判定为二维偏序问题(在坐标系上画一下)。二维偏序的解决方案也比较固定一般都是先将一维的顺序固定然后利用树状数组或者线段树处理第二维。这里我们利用线段树求解,因为涉及到区间更新和区间最大值。
const int N = 4e5 + 5;
int n , m , p;
ll c[N];
struct Node1 {
    int val;
    ll cost;
    bool operator < (const Node1& no) const {
        return val < no.val;
    }
}a[N], b[N];
// a攻击力 b防御力
bool cmp (Node1 x , Node1 y)
{
    if (x.val == y.val)
        return x.cost < y.cost;
    return x.val < y.val;
}
struct Node2 {
    // x 防御力 y 攻击力
    int x, y;
    ll val;
    bool operator < (const Node2& no) const {
        if (x == no.x)
            return y < no.y;
        return x < no.x;
    }
}mon[N];
struct trNode
{
    int l, r;
    ll maxn, val, tag;
}tr[N << 2];
void build (int i, int l, int r)
{
    tr[i].l = l, tr[i].r = r;
    tr[i].maxn = tr[i].val = tr[i].tag = 0;
    if (l == r)
        return ;
    int mid = (l + r) >> 1;
    build (i<<1, l, mid);
    build (i<<1|1, mid+1, r);
}
void push_down (int i)
{
    if (tr[i].tag)
    {
        tr[i<<1].tag += tr[i].tag;
        tr[i<<1|1].tag += tr[i].tag;
        tr[i<<1].maxn += tr[i].tag;
        tr[i<<1|1].maxn += tr[i].tag;
        tr[i].tag = 0;
    }
}
void update (int i , int l , int r , int L , int R, int k)
{
    if (l >= L && r <= R)
    {
        tr[i].maxn += k;
        tr[i].tag += k;
        return ;
    }
    push_down (i);
    int mid = (l + r) >> 1;
    if (L <= mid)
        update (i<<1,l,mid,L,R,k);
    if (R > mid)
        update (i<<1|1,mid+1,r,L,R,k);
    tr[i].maxn = max(tr[i<<1].maxn, tr[i<<1|1].maxn);
}
int main()
{
    CLOSE;
    cin >> n >> m >> p;
    for (int i = 1 ; i <= n ; i ++)
        cin >> a[i].val >> a[i].cost;
    for (int i = 1 ; i <= m ; i ++)
        cin >> b[i].val >> b[i].cost;
    for (int i = 1 ; i <= p ; i ++)
        cin >> mon[i].x >> mon[i].y >> mon[i].val;
    sort (a + 1 , a + 1 + n);
    sort (b + 1, b + 1 + m, cmp);
    sort (mon + 1 , mon + 1 + p);
    for (int i = 1 ; i <= m ; i ++)
        c[i] = b[i].val;
    int now = 1;
    ll ans = -1e18;
    build (1, 1, m);
    for (int i = 1 ; i <= m ; i ++)
        update (1, 1, m, i, i, -b[i].cost);
    for (int i = 1 ; i <= n ; i ++)
    {
        while (mon[now].x < a[i].val && now <= p)
        {
            int idx = upper_bound(c+1,c+1+m,mon[now].y) - c;
            update (1, 1, m, idx , m, mon[now].val);
            now ++;
        }
        ans = max (ans, tr[1].maxn - a[i].cost);
    }
    cout << ans << endl;
}

三.

  • 题目链接: http://codeforces.com/problemset/problem/652/D
  • 解题思路: 很显然的二维数点问题
const int N = 2e6 + 5;

int n;
struct BIT
{
    int tree[1000005];
    int ed = 1e6 + 5;
    void init()
    {
        mem(tree,0) ;
    }
    int lowbit(int k)
    {
        return k & -k;
    }
    void add(int x , int k) //注意值域
    {
        while(x <= ed)
        {
            tree[x] += k ;
            x += lowbit(x) ;
        }
    }
    int sum(int x)
    {
        int ans = 0 ;
        while(x != 0)
        {
            ans += tree[x] ;
            x -= lowbit(x) ;
        }
        return ans ;
    }
} bit ;
int ans[N];
struct node
{
    int x, y, id;
    bool operator < (const node& no) const {
        if (x == no.x)
            return y < no.y;
        return x > no.x;
    }
}p[N];
int a[N];
int main()
{
    CLOSE ;
    cin >> n;
    for (int i = 1 ; i <= n ; i ++)
        cin >> p[i].x >> p[i].y, p[i].id = i;
    sort (p + 1 , p + 1 + n);
    for (int i = 1 ; i <= n ; i ++)
        a[i] = p[i].y;
    sort (a + 1, a + 1 + n);
    int len = unique (a + 1 , a + 1 + n) - a - 1;
    for (int i = 1 ; i <= n ; i ++)
    {
        int idx = lower_bound(a+1,a+1+len,p[i].y) - a;
        ans[p[i].id] += bit.sum(idx);
        bit.add (idx, 1);
    }
    for (int i = 1 ; i <= n ; i ++)
        cout << ans[i] << endl;

四.

  • 题目链接: https://ac.nowcoder.com/acm/contest/9033/D
  • 解题思路: 先考虑弱化问题,只给定一个查询如何求解,我们暴力合法区间的每一个坐标,找出所有合法的区间数,我们发现这些区间一定是连续的,于是我们可以处理出每一个坐标对应的合法区间[x,y][x,y][x,y],然后就是一个很裸的二维数点问题了,这里注意,不能处理成离散的点,因为数量过多,需要进行区间修改,所以使用线段树
const int M = 4e5+5;
int n, q, k;
int a[M];
int tl[M], tr[M];
int ttl[M], ttr[M];
queue <int> Q[M];
struct Node1 {
    int x, y, id;
    bool operator < (const Node1& no) const {
        return y < no.y;
    }
}qes[M];
ll ans[M];
int vis[M];
struct tr_node {
    int l, r;
    ll tag, sum;
}qtr[M << 2];
void build (int i, int l, int r)
{
    qtr[i].l = l, qtr[i].r = r;
    qtr[i].sum = qtr[i].tag = 0;
    if (l == r)
        return ;
    int mid = (l + r) >> 1;
    build (i<<1, l, mid);
    build (i<<1|1, mid+1, r);
}
void push_down (int i)
{
    if (qtr[i].tag)
    {
        qtr[i<<1].tag += qtr[i].tag;
        qtr[i<<1|1].tag += qtr[i].tag;
        qtr[i<<1].sum += 1ll * qtr[i].tag * (qtr[i<<1].r - qtr[i<<1].l + 1);
        qtr[i<<1|1].sum += 1ll * qtr[i].tag * (qtr[i<<1|1].r - qtr[i<<1|1].l + 1);
        qtr[i].tag = 0;
    }
}
void update (int i , int l , int r , int L , int R, ll k)
{
    if (l >= L && r <= R)
    {
        qtr[i].sum += k * (r - l + 1);
        qtr[i].tag += k;
        return ;
    }
    push_down (i);
    int mid = (l + r) >> 1;
    if (L <= mid)
        update (i<<1,l,mid,L,R,k);
    if (R > mid)
        update (i<<1|1,mid+1,r,L,R,k);
    qtr[i].sum = qtr[i<<1].sum + qtr[i<<1|1].sum;
}
ll query (int i , int l , int r, int L, int R)
{
    ll sum = 0;
    if (l >= L && r <= R)
        return qtr[i].sum;
    push_down(i);
    int mid = (l + r) >> 1;
    if (L <= mid)
        sum += query (i << 1, l ,mid, L, R);
    if (R > mid)
        sum += query (i<<1|1, mid + 1, r, L, R);
    return sum;
}
int main()
{
    CLOSE;
    mem (ttl, INF);
    cin >> n >> q >> k;
    for (int i = 1 ; i <= n ; i ++)
        cin >> a[i];
    int st = 0, d = 0;
    for (int i = 1 ; i <= n ; i ++)
    {
        Q[a[i]].push (i);
        if (Q[a[i]].size() > k)
        {
            st = max (st, Q[a[i]].front());
            Q[a[i]].pop();
        }
        if (Q[a[i]].size() == k)
            d = max (d, Q[a[i]].front());
        if (d > 0)
            tl[i] = st + 1, tr[i] = d;
    }
    for (int i = 1 ; i <= q ; i ++)
        cin >> qes[i].x >> qes[i].y, qes[i].id = i;
    sort (qes + 1 , qes + 1 + q);
    build (1, 1, n);
    for (int i = 1 ; i <= q ; i ++)
    {
        for (int j = qes[i].y ; j >= 1 ; j --)
        {
            if (!vis[j])
            {
                vis[j] = 1;
                if (tl[j])
                    update (1, 1, n, tl[j], tr[j], 1);
            }
            else
                break;
        }
        ans[qes[i].id] = query (1, 1, n, qes[i].x, n);
    }
    for (int i = 1 ; i <= q ; i ++)
        cout << ans[i] << endl;

}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值