二维数点问题:
给定平面上的$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) #define x first #define y second using namespace std; typedef pair<int,int> pii; const int N = 1e6+10; int n, m, cnt; int a[N], pre[N], vis[N], ans[N]; struct _ { int x,y1,y2,id; bool operator < (const _ & rhs) const { return x<rhs.x; } } e[N]; pii b[N]; int c[N]; void add(int x, int v) { for (++x; x<=n+1; x+=x&-x) c[x]+=v; } 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; b[i] = {pre[i],i}; } scanf("%d", &m); REP(i,1,m) { int l, r; scanf("%d%d", &l, &r); e[++cnt] = {l-1,l,r,i}; } sort(e+1,e+1+cnt); sort(b+1,b+1+n); int now = 1; REP(i,1,cnt) { while (b[now].x<=e[i].x) add(b[now++].y,1); ans[e[i].id]+=query(e[i].y2)-query(e[i].y1-1); } REP(i,1,m) printf("%d\n",ans[i]); }
2. 在线主席树做法 (这题卡常很严重, 主席树过不去)
(1)按序列下标排序

#include <iostream> #include <algorithm> #include <cstdio> #include <math.h> #define REP(i,a,n) for(int i=a;i<=n;++i) #define lc tr[o].l #define rc tr[o].r #define mid ((l+r)>>1) #define ls lc,l,mid #define rs rc,mid+1,r using namespace std; const int N = 5e5+10; int n, a[N], pre[N], vis[N<<1]; int tot, m, T[N]; struct {int l,r,s;} tr[N<<5]; void update(int &o, int l, int r, int x) { tr[++tot]=tr[o],o=tot,++tr[o].s; if (l!=r) mid>=x?update(ls,x):update(rs,x); } int query(int u, int v, int l, int r, int ql, int qr) { if (ql<=l&&r<=qr) return tr[v].s-tr[u].s; int ans = 0; if (mid>=ql) ans+=query(tr[u].l,tr[v].l,l,mid,ql,qr); if (mid<qr) ans+=query(tr[u].r,tr[v].r,mid+1,r,ql,qr); return ans; } int main() { scanf("%d", &n); REP(i,1,n) { scanf("%d", a+i); pre[i] = vis[a[i]]; vis[a[i]] = i; T[i] = T[i-1]; update(T[i],0,n,pre[i]); } scanf("%d", &m); REP(i,1,m) { int l, r; scanf("%d%d", &l, &r); printf("%d\n", query(T[l-1],T[r],0,n,0,l-1)); } }
(2)按pre排序.

#include <iostream> #include <algorithm> #include <cstdio> #include <math.h> #define REP(i,a,n) for(int i=a;i<=n;++i) #define lc tr[o].l #define rc tr[o].r #define mid ((l+r)>>1) #define ls lc,l,mid #define rs rc,mid+1,r using namespace std; const int N = 5e5+10; int n, a[N], pre[N], vis[N<<1]; int tot, m, T[N]; struct {int l,r,s;} tr[N<<5]; pair<int,int> b[N]; void update(int &o, int l, int r, int x) { tr[++tot]=tr[o],o=tot,++tr[o].s; if (l!=r) mid>=x?update(ls,x):update(rs,x); } int query(int o, int l, int r, int ql, int qr) { if (!o||ql<=l&&r<=qr) return tr[o].s; int ans = 0; if (mid>=ql) ans+=query(ls,ql,qr); if (mid<qr) ans+=query(rs,ql,qr); return ans; } int main() { scanf("%d", &n); int mx = 0; REP(i,1,n) { scanf("%d", a+i); pre[i] = vis[a[i]]; vis[a[i]] = i; T[i] = T[i-1]; b[i] = {pre[i],i}; mx = max(mx, pre[i]); } sort(b+1,b+1+n); int now = 1; REP(i,0,mx) { if (i) T[i]=T[i-1]; while (b[now].first<=i) update(T[i],1,n,b[now++].second); } scanf("%d", &m); REP(i,1,m) { int l, r; scanf("%d%d", &l, &r); printf("%d\n", query(T[min(mx,l-1)],1,n,l,r)); } }
当然该题还可以用莫队或者维护每个数最后一次出现位置的方法.
例2. CF 848C Goodbye Souvenir
大意: 给定n元素序列, m个操作, 修改一个元素的值, 或者查询$[l,r]$范围内所有元素最后一次出现位置与第一次出现位置的差.
显然每次询问的答案为$\sum\limits_{\substack{l \le i \le r \\ l \le pre[i] \le r}}(i-pre[i])$, pre为上一次出现位置
动态二维数点问题, 不强制在线我们考虑用CDQ套树状数组.

#include <iostream> #include <algorithm> #include <cstdio> #include <math.h> #include <set> #include <map> #include <queue> #include <string> #include <string.h> #include <bitset> #define REP(i,a,n) for(int i=a;i<=n;++i) #define PER(i,a,n) for(int i=n;i>=a;--i) #define hr putchar(10) #define pb push_back #define lc (o<<1) #define rc (lc|1) #define mid ((l+r)>>1) #define ls lc,l,mid #define rs rc,mid+1,r #define x first #define y second #define io std::ios::sync_with_stdio(false) #define endl '\n' #define DB(a) ({REP(__i,1,n) cout<<a[__i]<<' ';hr;}) using namespace std; typedef long long ll; typedef pair<int,int> pii; const int P = 1e9+7, INF = 0x3f3f3f3f; ll gcd(ll a,ll b) {return b?gcd(b,a%b):a;} ll qpow(ll a,ll n) {ll r=1%P;for (a%=P;n;a=a*a%P,n>>=1)if(n&1)r=r*a%P;return r;} ll inv(ll x){return x<=1?1:inv(P%x)*(P-P/x)%P;} inline int rd() {int x=0;char p=getchar();while(p<'0'||p>'9')p=getchar();while(p>='0'&&p<='9')x=x*10+p-'0',p=getchar();return x;} //head const int N = 1e6+10; int n, m, a[N], pre[N], vis[N]; set<int> s[N]; struct _ { int type,x,y,v; bool operator < (const _ & rhs) const { if (x!=rhs.x) return x<rhs.x; if (y!=rhs.y) return y<rhs.y; return type<rhs.type; } void pr() { printf("tp=%d,x=%d,y=%d,v=%d\n",type,x,y,v); } } e[N]; int tot, totx; ll ans[N], c[N]; void add(int x, int v) { for (x+=2; x<=n+2; x+=x&-x) c[x]+=v; } ll qry(int x) { ll r = 0; for (x+=2; x; x^=x&-x) r+=c[x]; return r; } void merge(int l, int r) { if (l==r) return; merge(l,mid),merge(mid+1,r); int now = l; REP(i,mid+1,r) { while (now<=mid&&e[now].x<=e[i].x) { if (e[now].type==0) add(e[now].y,e[now].v); ++now; } if (e[i].type==1) ans[e[i].v]+=qry(e[i].y); if (e[i].type==2) ans[e[i].v]-=qry(e[i].y); } while (now!=l) { if (e[--now].type==0) add(e[now].y,-e[now].v); } inplace_merge(e+l,e+mid+1,e+r+1); } void ins(int x, int y) { pre[x] = y; e[++tot] = {0,x,pre[x],x-pre[x]}; } void del(int x) { e[++tot] = {0,x,pre[x],pre[x]-x}; } int main() { scanf("%d%d", &n, &m); REP(i,1,n) { scanf("%d", a+i); ins(i,vis[a[i]]); vis[a[i]] = i; s[a[i]].insert(i); } REP(i,1,m) { int op, x, y; scanf("%d%d%d", &op, &x, &y); if (op==1) { del(x); s[a[x]].erase(x); auto t = s[a[x]].upper_bound(x); if (t!=s[a[x]].end()) del(*t),ins(*t,pre[x]); s[y].insert(x); t = s[y].upper_bound(x); if (t!=s[y].end()) ins(x,pre[*t]),del(*t),ins(*t,x); else { if (s[y].size()==1) ins(x,0); else --t,--t,ins(x,*t); } a[x] = y; } else { ++totx; e[++tot] = {1,y,y,totx}; e[++tot] = {1,x-1,x-1,totx}; e[++tot] = {2,x-1,y,totx}; e[++tot] = {2,y,x-1,totx}; } } merge(1,tot); REP(i,1,totx) printf("%lld\n", ans[i]); }