二维数点问题


二维数点问题:

给定平面上的$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]);
}
View Code

(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]);
}
View Code

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));
    }
}
View Code

(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));
    }
}
View Code

当然该题还可以用莫队或者维护每个数最后一次出现位置的方法.

 

例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]);
}
View Code

 


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM