CDQ分治

gsczl71發表於2024-04-24

CDQ分治

它是一種非常巧妙地方法。

用分治的思想處理三維偏序一類的問題:

處理的問題如下:模板

有 $ 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 \ne i $ 的 \(j\) 的數量。

對於 $ d \in [0, n) $ ,求 $ f(i) = d $ 的數量。

也就是對於每一個三元組 \(i\) 看有多少個完全小於他的三元組 \(j\)

資料範圍 \(n \le 2\times 10^5\)

顯然暴力會不過去。

只能考慮一些 \(n \log n\)\(n \log^2 n\) 的做法。

首先,先把第一維排序了。

這樣,只用考慮 \(b_i\)\(c_i\) 了。

那我們考慮如何處理 \(b_i\)

類似歸併的方法,分而治之,分治。就是對於 \([l,r]\) 的子區間我們先處理 \([l,mid]\)\([mid+1,r]\) 兩個區間,然後將其 \(O(r-l)\) 的時間複雜度合併。很簡單的策略,分別在兩個子區間內放兩個指標,那邊小就加入那邊,因為如果 \(A_i\) 他是比 \(B_j\) 小的,那麼 \(A\) 目前也排序了,所以也是 \(A\) 中最小的,也就是 \(A\)\(B\) 合併後最小的,因此直接判斷然後去加即可。

這樣,\(b_i\) 也處理好了,我們就可以用樹狀陣列維護一下 \(c_i\) 的數量,然後就可以求出此時比 \(c_i\) 小的 \(c_j\) 有多少個,由於線上處理,所以說,之前出現的 \(a_i\)\(b_i\) 都是應該符合小於現在的這個三元組的。

於是,線上(邊跑邊記錄)處理完之後,再依次輸出答案。

int n,k,m;
struct node{int a,b,c;};
struct data{node a;int cnt,ans;};
node a[N];
struct data b[N];
int t[N<<1];
void add(int x,int val){while(x<=k)t[x]+=val,x+=(x&-x);}
int query(int x){int res=0;while(x)res+=t[x],x-=(x&-x);return res;}
bool node_cmp(node a,node b){
	if(a.a != b.a) return a.a<b.a;
	if(a.b != b.b) return a.b<b.b;
	return a.c<b.c;
}bool data_cmp(struct data a,struct data b){
	if(a.a.b != b.a.b) return a.a.b<b.a.b;
	return a.a.c<b.a.c;
}void cdq(int l,int r){
	if(l==r)return ;
	int mid=(l+r)>>1,x=l;
	cdq(l,mid),cdq(mid+1,r);
	sort(b+l,b+1+mid,data_cmp),sort(b+1+mid,b+1+r,data_cmp);
	For(i,mid+1,r){
		while(x<=mid&&b[x].a.b<=b[i].a.b) add(b[x].a.c,b[x].cnt),x++;
		b[i].ans+=query(b[i].a.c);
	}For(i,l,x-1){
		add(b[i].a.c,-b[i].cnt);//清空還原操作
	}
}int ans[N];
void solve(){
	cin>>n>>k;
	For(i,1,n)cin>>a[i].a>>a[i].b>>a[i].c;
	sort(a+1,a+1+n,node_cmp);
	For(i,1,n){
		if(a[i].a!=a[i-1].a||a[i].b!=a[i-1].b||a[i].c!=a[i-1].c) b[++m].a=a[i];
		b[m].cnt++;	
	}
	cdq(1,m);
	For(i,1,m)ans[b[i].ans+b[i].cnt]+=b[i].cnt;
	For(i,1,n)cout<<ans[i]<<endl;
}

相關文章