1. 程式人生 > >淺談CDQ分治

淺談CDQ分治

區間 裝逼 def r+ cdq分治 最終 bitset main 二分

Ⅰ、預備知識

整體二分???

Ⅱ、拋出問題

我們先來看一道洛谷的模板題

題目背景

這是一道模板題
可以使用bitset(不會),CDQ分治,K-DTree(不會)等方式解決。

題目描述

\(n\)個元素,第\(i\)個元素有\(a_i\)\(b_i\)\(c_i\)三個屬性,設\(f(i)\)表示滿足\(a_j\leq a_i\)\(b_j\leq b_i\)\(c_j\leq c_i\)\(j\)的數量。
對於\(d\in[0, n)\),求\(f(i)=d\)的數量

輸入輸出格式

輸入格式:

第一行兩個整數\(n、k\),分別表示元素數量和最大屬性值。
之後\(n\)行,每行三個整數\(a_i\)

\(b_i\)\(c_i\),分別表示三個屬性值。

輸出格式:

輸出\(n\)行,第\(d+1\)行表示\(f(i)=d\)\(i\)的數量。

輸入輸出樣例

輸入樣例#1:

10 3
3 3 3
2 3 3
2 3 1
3 1 1
3 1 2
1 3 1
1 1 2
1 2 2
1 3 2
1 2 1

輸出樣例#1:

3
1
3
0
1
0
1
0
0
1

說明:

\(1\leq N\leq100000,1\leq k\leq200000\)

Ⅲ、分析問題

CDQ分治,有國家隊某巨佬發明(仿佛是插頭dp的論文作者???),主要用於解決帶修改,查詢,可排序序列的一系列問題,僅可支持離線操作
CDQ分治的主要步驟有以下幾點:

1、讀入(廢話)
1、將已經讀入好的數據按照某關鍵字排序
2、設當前區間為\([l,r]\),遞歸處理左區間\([l,mid]\)和右區間\([mid+1,r]\),計算左區間的修改操作對右區間的影響(一般用樹狀數組等數據結構維護)
3、清除數據結構內的修改數據
本題又叫三維偏序問題,是CDQ分治的經典題型
先按照第一維(即\(a_i\))排序,這樣就將問題轉化到了二維
設當前區間為\([l,r]\)
\([l,mid]\)\([mid+1,r]\)分別按照第二維排序,此時在左區間中的\(a\)均小於有區間中的\(a\)(保證第一維),設左區間已訪問到\(pl\),右區間已訪問到\(pr\)
\((l\leq pl\leq mid,mid+1\leq pr\leq r)\)
\(b[pl]<=b[pr]\)時(保證第二維),即將\(pl\)點的\(c\)值加入樹狀數組
統計比\(pr\)點的\(c\)值小或等於的點的數量(保證第三維)
詳見代碼

#include<bits/stdc++.h>
#define ll long long
#define INF 2147483647
#define mem(i,j) memset(i,j,sizeof(i))
#define F(i,j,n) for(register int i=j;i<=n;i++)
#define lowbit(i) i&(-i)//樹狀數組
using namespace std;
struct hahaha{
    int x,y,z,ans,cnt;//x,y,z分別對應a,b,c;ans表示題目中的f(i),即三維都小於等於i的數量,cnt表示x,y,z相等的點的數量,若只出現一次,則cnt=1
}f[100010],s[100010];//f為輸入數據,s為處理後數據
int nn,n,m,ans[100010],c[200010];//c是樹狀數組上的點,ans為最終答案
inline int read(){
    int datta=0;char chchc=getchar();bool okoko=0;
    while(chchc<'0'||chchc>'9'){if(chchc=='-')okoko=1;chchc=getchar();}
    while(chchc>='0'&&chchc<='9'){datta=datta*10+chchc-'0';chchc=getchar();}
    return okoko?-datta:datta;
}
inline bool cmpx(hahaha a,hahaha b){//以x為第一關鍵字排序
    return a.x==b.x?a.y==b.y?a.z<b.z:a.y<b.y:a.x<b.x;
}
inline bool cmpy(hahaha a,hahaha b){//以y為第一關鍵字排序
    return a.y==b.y?a.z<b.z:a.y<b.y;
}
inline void add(int x,int v){//樹狀數組修改
    for(int i=x;i<=m;i+=lowbit(i))
        c[i]+=v;
}
inline int ask(int x){//樹狀數組查詢
    int res=0;
    for(int i=x;i;i-=lowbit(i))
        res+=c[i];
    return res;
}
class CDQ_DC{//之所以用class寫是為了裝逼
    private:
    public:
    inline void CDQ(int l,int r){
        if(l==r)//邊界條件
            return ;
        int mid=(l+r)>>1;
        CDQ(l,mid);
        CDQ(mid+1,r);//遞歸處理左右區間
        sort(s+l,s+mid+1,cmpy);
        sort(s+mid+1,s+r+1,cmpy);//按y排序
        int pl=l,pr=mid+1;
        while(pr<=r){
            while(pl<=mid&&s[pl].y<=s[pr].y)
                add(s[pl].z,s[pl].cnt),pl++;//加點
            s[pr].ans+=ask(s[pr].z);//處理pr的ans
            pr++;
        }
        F(i,l,pl-1)
            add(s[i].z,-s[i].cnt);//清空樹狀數組
    }
}C;
int main(){
    nn=read();m=read();
    F(i,1,nn)
        f[i].x=read(),f[i].y=read(),f[i].z=read();
    sort(f+1,f+nn+1,cmpx);//按x排序
    int ct=0;
    F(i,1,nn){
        ct++;
        if(f[i].x!=f[i+1].x||f[i].y!=f[i+1].y||f[i].z!=f[i+1].z){
            s[++n]=f[i];
            s[n].cnt=ct;//處理數據
            ct=0;
        }
    }
    C.CDQ(1,n);
    F(i,1,n)
        ans[s[i].ans+s[i].cnt-1]+=s[i].cnt;//處理最後答案
    F(i,0,nn-1)
        printf("%d\n",ans[i]);
    return 0;
}

淺談CDQ分治