Ⅰ、預備知識
整體二分???
Ⅱ、拋出問題
我們先來看一道洛谷的模板題
題目背景
這是一道模板題
可以使用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;
}