【三维cdq】P3810 【模板】三维偏序(陌上花开)

CDQ算法 同时被 3 个专栏收录
2 篇文章 0 订阅
7 篇文章 0 订阅
1 篇文章 0 订阅

三维偏序cdq:题目中明显说了,三个维度(a, b, c)

题目链接

这里有必要说明,在一些二维cdq的题目中有题中指定的操作的时间作为第一个维度,这是容易看的,因为对于操作对于后续操作的影响必定要让时间早的操作优先执行,这样才能够保证方法的准确性,相当于弱化了第一个维度存在的含义。但是这里就是要把第一个维度与其他维度等价考虑

这个题目有细节地方的考虑,下面再说,这边先说概括性的思路。

有三个维度,我们知道cdq函数在递归调用返回之后,左边一半的区间和右边一半的区间需要有一个维度是所有的右值都大于(小于也行)左边的值的对应维度,(这个在二维cdq中的体现就是时间的顺序), 那么对于这道题我们可以排第一个维度a 的顺序, 那么对于左边和右边的区间, 双指针可以用在b维度上,效果就是如果左边的b值比右值的b值小于或者等于,那么已知左边的a值小于右边的a值,这样就是在忽略c值的基础上已经满足题意,那么c值怎么考虑呢?,权值树状数组,把左值a右值a小,同时左值b右值b小的左值的c 加在权值树状数组中,如果遇到左值的b开始大于右值的b,那么说明左边可能的c值全部放在树状数组中了,先拿当前右值的c去树状数组中查询查到前缀中有几个值就是满足题意的点的个数了。

这个题的细节

  • 要考虑多个点在同一个位置的情况,这个怎么处理呢?先去重,统计这个值位置有几个点作为权值,那么加入树状数组的时候是按照下标加入当前的右值的权值。
  • 权值线段树在每一个递归的函数中都要从全部空开始用, 但是用memset应该会超时, 因为递归的层数会让这个时间变大很多很多,所以每一次加了什么值,加在什么位置都要记录下来,用完之后用这些存下来的记录加上赋值即可达到清空的作用。

我是按照坐标自己也可以算作自己的一个满足题意的解做的,在输出的时候就从1开始,所以结果与题目中的意思是等价的。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define min(i, j) (i < j ? i : j)
#define max(i, j) (i > j ? i : j)
const int maxn = 2e6 + 10;
struct BITree
{
private:
    ll savedata[maxn];
    int n;
    inline int lowbit(int pos) { return pos & (-pos); }

public:
    inline void init(int n1)
    {
        n = n1;
        memset(savedata, 0, sizeof(savedata));
    }
    inline void add(int pos, ll val)
    {
        while (pos <= n)
        {
            savedata[pos] += val;
            pos += lowbit(pos);
        }
    }
    inline ll sum(int pos)
    {
        ll res = 0;
        while (pos > 0)
        {
            res += savedata[pos];
            pos -= lowbit(pos);
        }
        return res;
    }
} tr;

struct node
{
    int id;
    int a, b, c;
    int cnt;
    bool operator<(const node &n) const
    {
        if (a != n.a)
            return a < n.a;
        else if (b != n.b)
            return b < n.b;
        else
        {
            return c < n.c;
        }
    }
    bool operator==(const node &n) const
    {
        return a == n.a && b == n.b && c == n.c;
    }
} saveori[maxn], temp[maxn];

ll ans[maxn];
ll ans1[maxn];
void cdq(int l, int r)
{
    if (l == r)
    {
        ans[saveori[l].id] += saveori[l].cnt;
        return;
    }
    int mid = (l + r) / 2;
    cdq(l, mid);
    cdq(mid + 1, r);

    int larr = l;
    int rarr = mid + 1;
    vector<pair<int, int>> se;

    //左边的a全部比右边的a小, 对于每一边,b的值单调递增, c的值不确定
    while (larr <= mid && rarr <= r)
    {
        node &lo = saveori[larr];
        node &ro = saveori[rarr];
        if (lo.b <= ro.b)
        {
            tr.add(lo.c, lo.cnt);
            se.push_back({lo.c, lo.cnt});
            larr++;
        }
        else
        {
            ans[ro.id] += tr.sum(ro.c);
            rarr++;
        }
    }

    while (rarr <= r)
    {
        node &ro = saveori[rarr++];
        ans[ro.id] += tr.sum(ro.c);
    }
//原则上来说树状数组每次必须全部清空,但是如果memset那么乘上递归的次数,应该会超时。
//所以把前面加入的位置和值都存起来,这里加上对应的负值即可达到清空的目的
    for (pair<int, int> v : se)
    {
        tr.add(v.first, -v.second);
    }

    larr = l;
    rarr = mid + 1;
    int tempid = l;
    while (larr <= mid && rarr <= r)
    {
        node &lo = saveori[larr];
        node &ro = saveori[rarr];
        if (lo.b < ro.b)
        {
            temp[tempid++] = lo;
            larr++;
        }
        else if (lo.b == ro.b)
        {
            if (lo.c < ro.c)
            {
                temp[tempid++] = lo;
                larr++;
            }
            else
            {
                temp[tempid++] = ro;
                rarr++;
            }
        }
        else
        {
            temp[tempid++] = ro;
            rarr++;
        }
    }

    while (larr <= mid)
    {
        temp[tempid++] = saveori[larr++];
    }
    while (rarr <= r)
    {
        temp[tempid++] = saveori[rarr++];
    }
    for (int i = l; i <= r; i++)
    {
        saveori[i] = temp[i];
    }
}

int main()
{
    // freopen("in.txt", "r", stdin);
    int n, k;
    cin >> n >> k;
    tr.init(k + 90);
    for (int i = 1; i <= n; i++)
    {
        int a, b, c;
        cin >> a >> b >> c;
        saveori[i].a = a;
        saveori[i].b = b;
        saveori[i].c = c;
        saveori[i].id = i;
        saveori[i].cnt = 1;
    }
    sort(saveori + 1, saveori + 1 + n);

    int larr = 1;
    int rarr = 2;
    while (rarr <= n)
    {
        if (saveori[larr] == saveori[rarr])
        {
            saveori[larr].cnt++;
            rarr++;
        }
        else
        {
            larr++;
            saveori[larr] = saveori[rarr];
            rarr++;
        }
    }
    cdq(1, larr);

    for (int i = 1; i <= larr; i++)
    {
        ans1[ans[saveori[i].id]] += saveori[i].cnt;
    }
    for (int i = 1; i <= n; i++)
    {
        cout << ans1[i] << endl;
    }
}
  • 0
    点赞
  • 0
    评论
  • 1
    收藏
  • 打赏
    打赏
  • 扫一扫,分享海报

参与评论
请先登录 后发表评论~
©️2021 CSDN 皮肤主题: 数字20 设计师:CSDN官方博客 返回首页

打赏作者

Π鱼星先生

你的鼓励将是我创作的最大动力

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值