二维数点问题:
给定平面上的$n$个点$(x_i,y_i)$, 权值$f(x_i,y_i)$, $m$次矩形查询$\sum\limits_{\substack{a \le i \le b \\ c \le j \le d}}f(i,j)$
以下记$S(a,b)=\sum\limits_{\substack{i \le a \\ j \le b}}f(i,j)$
一般解法:
先将矩形查询拆成四个二维前缀和S(b,d)-S(a-1,d)-S(b,c-1)+S(a-1,c-1), 从而转化为偏序问题.
对于静态的二维数点, 等价于二维偏序问题, 离线可以用树状数组, 在线可以主席树.
对于动态的二维数点, 等价于三维偏序问题, 离线可以用CDQ套树状数组, 在线可以树套树.
例1. luogu P1972 [SDOI2009]HH的项链
大意: 给定$n$元素序列$a$, $m$个询问, 求区间$[l,r]$内不同元素的个数.
静态二维数点板子题, 可以转化为求$\sum\limits_{\substack{l \le i \le r \\ 0 \le pre[i] \le l-1}}1$, 其中$pre[x]$为$a[x]$上一次出现位置
我们取坐标轴为序列的下标和pre数组, $a$中每个元素看成点$(i,pre[i])$, 那么就是一个标准的二维数点问题, 先考虑离线做法.
1. 离线做法一般是先将所有点和询问按一个坐标轴排序, 然后用树状数组算贡献.
(1)按序列下标排序.
点已经有序, 对于询问$[l,r]$, 拆成前缀形式S(r,l-1)-S(l-1,l-1)再排序, 用树状数组维护pre的贡献


#include <iostream> #include <algorithm> #include <cstdio> #define REP(i,a,n) for(int i=a;i<=n;++i) using namespace std; const int N = 1e6+10; int n, m, cnt; int a[N], pre[N], vis[N], ans[N]; struct _ { int type,x,y,id; bool operator < (const _ & rhs) const { return x<rhs.x; } } e[N]; int c[N]; void add(int x) { for (++x; x<=n+1; x+=x&-x) ++c[x]; } int query(int x) { int r = 0; for (++x; x; x^=x&-x) r+=c[x]; return r; } int main() { scanf("%d", &n); REP(i,1,n) { scanf("%d", a+i); pre[i] = vis[a[i]]; vis[a[i]] = i; } scanf("%d", &m); REP(i,1,m) { int l, r; scanf("%d%d", &l, &r); e[++cnt] = {-1,l-1,l-1,i}; e[++cnt] = { 1,r,l-1,i}; } sort(e+1,e+1+cnt); int now = 1; REP(i,1,cnt) { while (now<=e[i].x) add(pre[now++]); ans[e[i].id]+=e[i].type*query(e[i].y); } REP(i,1,m) printf("%d\n",ans[i]); }
(2)按pre排序.


#include <iostream> #include <algorithm> #include <cstdio> #define REP(i,a,n) for(int i=a;i<=n;++i)