洛谷 P3810 【模板】三维偏序(陌上花开) 题解

一、题目:

洛谷原题

二、思路:

很明显这是一道CDQ分治的模板题。在这里简单讲一下CDQ分治的大概思路。

CDQ分治的目的是将动态的修改、查询离线,从而转化为静态的操作。具体来说是这样的:

假设(a)是一个修改、查询的序列。定义(CDQ(l,r))为处理(a[lsim r])的函数。

然后我们发现(a[lsim r])的修改和查询分别可以分成两类。

修改 查询
处于[l,mid]的 处于[l,mid]的
处于[mid+1,r]$的 处于[l,mid]的

我们发现对于一个查询(a[x]),可以看成是(a[1sim (x-1)])内的所有修改操作叠加起来造成的一个“影响”。那么我们在求解(CDQ(l,r))的时候,可以先处理(CDQ(l, mid)),再处理(CDQ(mid+1,r))

现在我们来看一看(CDQ(l,r))的求解情况。

类别 处于[l,mid]的修改 处于[mid+1,r]的修改
处于[l,mid]的查询 已完成 ——
处于[mid+1,r]的查询 未完成 已完成

接下来的问题就是如何处理([l,mid])的修改对([r,mid+1])的查询造成的影响,可以使用其他算法解决,只要该算法的时间复杂度和区间长度有关。

对于本题来说,我习惯将本题的(a)称为(x)(b)称为(y)(c)称为(z)

先别考虑坐标相同的情况。一开始就按(x)坐标排序,这时候,每新加一个点就相当于先查询一次,再操作一次。我们于是就可以采用CDQ分治。

那么究竟如何处理([l,mid])的修改对([r,mid+1])的查询造成的影响呢?可以用树状数组等数据结构,在这里不是我们强调的重点,详见代码。

三、代码:

//P3810
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>

using namespace std;

#define LL long long
#define mem(s, v) memset(s, v, sizeof s)
#define FILEIN(s) freopen(s".in", "r", stdin)
#define FILEOUT(s) freopen(s".out", "w", stdout)

inline int read(void) {
    int x = 0, f = 1; char ch = getchar();
    while (ch < '0' || ch > '9') { if (ch == '-') f = -1; ch = getchar(); }
    while (ch >= '0' && ch <= '9') { x = x * 10 + ch - '0'; ch = getchar(); }
    return f * x;
}

const int maxn = 1e5 + 5, maxm = 2e5 + 5;

int n, K, cnt, ans[maxn];

struct Node {
    int x, y, z;
    int v, ans, tag, id;
    Node() { ans = tag = v = x = y = z = 0; }
    inline friend bool operator == (const Node &a, const Node &b) {
        return (a.x == b.x) && (a.y == b.y) && (a.z == b.z);
    }
}a[maxn], t[maxn];

inline bool cmp1(const Node &a, const Node &b) {
    if (a.x != b.x) return a.x < b.x;
    if (a.y != b.y) return a.y < b.y;
    return a.z < b.z;
}

inline bool cmp2(const Node &a, const Node &b) {
	if (a.y != b.y) return a.y < b.y;
	if (a.tag != b.tag) return a.tag < b.tag;
	return a.id < b.id;
}

inline int lowbit(int x) { return x & (-x); }

int tr[maxm];

inline void add(int p, int x) {
    for (; p <= K; p += lowbit(p)) tr[p] += x;
}

inline int query(int p) {
    int ret = 0;
    for (; p; p -= lowbit(p)) ret += tr[p];
    return ret;
}

void CDQ(int l, int r) {
    if (l == r) return;
    int mid = (l + r) >> 1;
    CDQ(l, mid); CDQ(mid + 1, r);
    for (register int i = l; i <= r; ++i) a[i].id = i;
    for (register int i = l; i <= mid; ++i) a[i].tag = 0;
    for (register int i = mid + 1; i <= r; ++i) a[i].tag = 1;
    sort(a + l, a + r + 1, cmp2);
    for (register int i = l; i <= r; ++i) {
    	if (!a[i].tag) add(a[i].z, a[i].v);
    	else a[i].ans += query(a[i].z);
	}
	for (register int i = l; i <= r; ++i) if (!a[i].tag) add(a[i].z, -a[i].v);
}

int main() {
    n = read(); K = read();
    for (register int i = 1; i <= n; ++i) {
        a[i].x = read(); a[i].y = read(); a[i].z = read();
        a[i].v = 1;
    }
    sort(a + 1, a + n + 1, cmp1);
    cnt = 1;
    for (register int i = 2; i <= n; ++i) {
        if (a[i] == a[cnt]) ++a[cnt].v;
        else a[++cnt] = a[i]; 
    }
/*
在这里想说一下坐标相同的情况。事实上当x坐标相同的情况发生的时候,我们唯一担心的是区间[mid+1,r]的修改对区间[l,mid]的查询造成影响。
但是不用担心,我们按照x为第一关键字,y为第二关键字,z为第三关键字排序,可以避免这种情况的发生。
只有一种情况需要特别注意,那就是x、y、z都相同的时候。所以我们对这种点进行压缩处理,就OK啦!
*/
    CDQ(1, cnt);
    for (register int i = 1; i <= cnt; ++i) ans[a[i].ans + a[i].v - 1] += a[i].v;
    for (register int i = 0; i < n; ++i) printf("%d
", ans[i]);
    return 0;
}
原文地址:https://www.cnblogs.com/little-aztl/p/14240270.html