淺談CDQ分治與偏序問題

初識CDQ分治

CDQ分治是一個好東西,一直聽着dalao們說因此就去學了下。git

CDQ分治是咱們處理各種問題的重要武器。它的優點在於能夠頂替複雜的高級數據結構,並且常數比較小;缺點在於必須離線操做。 ——by __stdcall數組

其實CDQ分治名字聽上去很高大上,其實和通常的分治沒有特別大的區別,其大致流程以下:數據結構

  1. 將問題抽象爲一個區間\([l,r]\)內的問題(廢話)
  2. 分:將問題分解成左\([l,mid]\)\([mid+1,r]\)兩部分,而後遞歸操做
  3. 治:合併兩個子問題,同時考慮到\([l,mid]\)內的修改對\([mid+1,r]\)內的查詢產生的影響。即,用左邊的子問題幫助解決右邊的子問題。

這裏特別注意CDQ分治與通常分治的區別:普通分治在合併兩個子問題的過程當中,左右區間內的問題不會互相影響。spa


經典應用——三維偏序

咱們從一道模板題來看看CDQ的具體實現:Luogu P3810指針

首先考慮經典的二維偏序:逆序對code

這個鬼東西不是就一個歸併排序or權值樹狀數組的事情麼排序

咱們想一下歸併排序的原理,在歸併的過程當中(數組已經有序),那麼我左邊的而且座標大於右邊的座標個數其實就是逆序對個數。遞歸

所以這也算是個簡單的CDQ吧get

如今咱們考慮三維偏序,咱們考慮先對數組整體排個序,這樣在操做的過程當中總有\(a_i\le a_j(i<j)\)(即便咱們將區間一分爲二那麼右邊的數的\(a_i\)始終大於左邊。it

而後對於第二維\(y_i\),咱們考慮一下處理方法。

假設如今處理區間\([l,r]\),而此前咱們已經經過遞歸處理好了\([l,mid]\)\([mid+1,r]\)的答案。

那咱們把\([l,mid]\)\([mid+1,r]\)分別按\(y_i\)排個序,這樣第二維也有了上面的性質。

再考慮怎麼計算左邊和右邊的偏序關係,咱們能夠維護兩個指針\(i,j\),每次咱們將\(j\)後移一位以表示再加入一個數,此時若\(y_i\le y_j\)則不斷後移\(i\),而且將\(z_i\)加入權值樹狀數組。

而後如今對於右邊的每個數:

  • 在權值樹狀數組上全部的數的\(x_i\)都小於它(由於排了序)
  • 在權值樹狀數組上全部的數的\(y_i\)都小於它(由於上面的指針偏移統計)

那麼只要找\(z_i\)小於它的數個數便可,這個咱們直接在樹狀數組上找便可。

複雜度是比較迷的\(O(n\log n)\),不過因爲CQD的常數很小因此能夠輕鬆跑過緬懷各位寫樹套樹的dalao

下面上CODE

#include<cstdio>
#include<cctype>
#include<algorithm>
using namespace std;
const int N=100005;
struct data
{
    int x,y,z,num,sum;
    bool operator ==(const data &s) const { return x==s.x&&y==s.y&&z==s.z; }
}a[N],q[N];
int n,cnt,m,bit[N<<1],ans[N],tot;
inline char tc(void)
{
    static char fl[100000],*A=fl,*B=fl;
    return A==B&&(B=(A=fl)+fread(fl,1,100000,stdin),A==B)?EOF:*A++;
}
inline void read(int &x)
{
    x=0; char ch; int flag=1; while (!isdigit(ch=tc())) flag=ch^'-'?1:-1;
    while (x=(x<<3)+(x<<1)+ch-'0',isdigit(ch=tc())); x*=flag;
}
inline void write(int x)
{
    if (x>9) write(x/10);
    putchar(x%10+'0');
}
inline bool cmpx(data a,data b)
{
    if (a.x==b.x&&a.y==b.y) return a.z<b.z;
    if (a.x==b.x) return a.y<b.y; return a.x<b.x;
}
inline bool cmpy(data a,data b)
{
    if (a.y==b.y) return a.z<b.z; return a.y<b.y;
}
inline int lowbit(int x)
{
    return x&-x;
}
inline void add(int x,int y)
{
    for (;x<=m;x+=lowbit(x)) bit[x]+=y;
}
inline int get(int x)
{
    int res=0; for (;x;x-=lowbit(x)) res+=bit[x]; return res;
}
inline void CDQ(int l,int r)
{
    if (l==r) return; int mid=l+r>>1,id=l;
    CDQ(l,mid); CDQ(mid+1,r); sort(q+l,q+mid+1,cmpy); sort(q+mid+1,q+r+1,cmpy);
    for (register int i=mid+1;i<=r;++i)
    {
        while (id<=mid&&q[id].y<=q[i].y) add(q[id].z,q[id].num),++id; q[i].sum+=get(q[i].z);
    }
    for (register int i=l;i<id;++i) add(q[i].z,-q[i].num);
}
int main()
{
    //freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
    register int i; read(n); read(m);
    for (i=1;i<=n;++i) read(a[i].x),read(a[i].y),read(a[i].z);
    for (sort(a+1,a+n+1,cmpx),a[n+1]=(data){-1,-1,-1},i=cnt=1;i<=n;++i)
    if (a[i]==a[i+1]) ++cnt; else q[++tot]=a[i],q[tot].num=cnt,cnt=1;
    for (CDQ(1,tot),i=1;i<=tot;++i) ans[q[i].sum+q[i].num-1]+=q[i].num;
    for (i=0;i<n;++i) write(ans[i]),putchar('\n'); return 0;
}

關於更復雜的問題

其實我也不會,不過對於通常的高維偏序,咱們能夠CDQ套CDQ,反正通常k維偏序用CDQ的複雜度就是\(O(n\log^{k-1} n)\)

所以維數太大時仍是使用K-d tree

相關文章
相關標籤/搜索