CDQ分治
CDQ分治和整體二分都是基於分治思想的,把複雜的問題拆成許多可以簡單求解的子問題,但必須是離線的
普通分治
一維偏序
時間複雜度\(O(nlogn)\)
逆序對,樹狀陣列或者歸併排序去求
注意用樹狀陣列時離散化一下,即可。先初始化id,然後排完序後再賦值排名就行了
void MergeSort(int l,int r){ if(l == r)return; int mid = (l + r) >> 1; MergeSort(l, mid);//左邊 MergeSort(mid+1,r);//右邊 int i = l,j = mid + 1, t = l;//i是左邊的,j是右邊的 while(i <= mid && j <= r){ if(a[i] <= a[j])b[t++] = a[i++];//左邊小 else b[t++] = a[j++],ans += mid - i + 1;//[i, mid]都比a[j]大 } while(i <= mid)b[t++] = a[i++];//右邊部分多 while(j <= r)b[t++] = a[j++];//左邊部分多 for(int k = l;k <= r; k++)a[k] = b[k];//及時更新 }
二維偏序
時間複雜度\(O(nlogn)\)
有n個元素,每個元素有\(a_i,b_i\)兩個屬性,設\(f(i)\)表示滿足\(a_j ≤a_i\)且\(b_j≤b_i\)且\(j≠i\)的數量。對於\(d∈[0,n)\),求滿足\(f(i) = d\)的數量
首先對a進行排序,如果a相同,就按照b排序。
然後按排好的順序,先查詢[1,i]裡b比自己小的,然後把自己加進去。
因為已經按照a軸排序了。那麼在我查詢[1,i]裡b比自己小的數量時,已經保證所有的a都比自己小了。而對於字首和查詢+單點修改,可以用樹狀陣列進行維護。
樹狀陣列
#include <iostream> #include <cstdio> #include <algorithm> using namespace std; const int N = 1e5 + 5; int n, c[N], b[N]; struct Stars{ int x,y; }a[N]; bool cmp(Stars a,Stars b){ if(a.x == b.x)return a.y < b.y; return a.x < b.x; } int lowbit(int x){ return x & (-x); } void add(int i,int k){ for(; i <= N; i += lowbit(i))c[i] += k; } int sum(int i){ int ans = 0; for(; i; i -= lowbit(i))ans += c[i]; return ans; }
離散化
因為對於每個節點的下標就代表屬性值,而資料範圍又會很大,需要離散化
scanf("%d",&n); for(int i = 1; i <= n; i++) scanf("%d%d",&a[i].x,&a[i].y),b[i] = a[i].y; sort(b + 1, b + n + 1); int q = unique(b + 1, b + n + 1) - b - 1; for(int i = 1; i <= n; i++) a[i].y = lower_bound(b + 1, b + n + 1, a[i].y) - b; sort(a + 1, a + n + 1, cmp);
維護
因為本身不能算,所以是先sum再add
正向可以求出x和y都比本身小的數的個數為\(now\)
反向可以求出x和y都比本身大的數的個數為\(n - i+now\)
for(int i = 1; i <= n; i++){
int now = sum(a[i].y);//找到y值≤自己的個數
add(a[i].y, 1);
}
如果是求滿足二維偏序的i的個數
倒著維護得話,now得到的是\(x\)比自己大,但是y比自己小的個數,其中n - i表示的是\(x\)比自己大的數,那麼\(n - i - now\)就是兩個屬性都比自己大的數
for(int i = n; i >= 1; i--){
int now = sum(a[i].y);
if(n - i - now > 0)ans++;
}
模板
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
const int N = 1e5 + 5;
int n, c[N], b[N];
struct Stars{
int x,y;
}a[N];
bool cmp(Stars a,Stars b){
if(a.x == b.x)return a.y < b.y;
return a.x < b.x;
}
int lowbit(int x){
return x & (-x);
}
void add(int i,int k){
for(; i <= N; i += lowbit(i))c[i] += k;
}
int sum(int i){
int ans = 0;
for(; i; i -= lowbit(i))ans += c[i];
return ans;
}
int main(){
scanf("%d",&n);
for(int i = 1; i <= n; i++)scanf("%d%d",&a[i].x,&a[i].y),b[i] = a[i].y;
sort(b + 1, b + n + 1);
int q = unique(b + 1, b + n + 1) - b - 1;
for(int i = 1; i <= n; i++)
a[i].y = lower_bound(b + 1, b + n + 1, a[i].y) - b;
sort(a + 1, a + n + 1, cmp);
for(int i = 1; i <= n; i++){
int now = sum(a[i].y);
add(a[i].y, 1);
}
return 0;
}
擴充套件
在一個地圖上有n個點,m個查詢,查詢\((x_1,y_1),(x_2,y_2)\)這個矩陣裡有幾個點。
把每個查詢(分成4塊,最後利用字首和思想求)和每個點都加入query結構體,然後排序求,如果是加入的點,opt為1,查詢則不加。排序,第一關鍵詞x,第二關鍵詞y,最後如果是查詢就放在點的後面
#include <cstdio>
#include <cctype>
#include <algorithm>
using namespace std;
const int maxq = 3e6 + 5;
struct Ques{
int x,y,opt,id;//opt表示點的型別,1為修改點,0為詢問點
bool operator < (const Ques &b)const{//排序
return x == b.x ? (y == b.y ? opt : y < b.y) : x < b.x;
}
}ques[maxq],tmp[maxq];
int ques_tot,ans_tot,ans[maxq];
void cdq(int a,int b){//二維偏序
if(a == b)return;
int mid = (a + b) >> 1;
cdq(a,mid),cdq(mid + 1,b);
int posa = a,posb = mid + 1,pos = a,tot = 0;
while(posa <= mid && posb <= b){
if(ques[posa].y <= ques[posb].y)tot += ques[posa].opt,tmp[pos++] = ques[posa++];
else ans[ques[posb].id] += tot,tmp[pos++] = ques[posb++];
}
while(posa <= mid)tmp[pos++] = ques[posa++];
while(posb <= b)ans[ques[posb].id] += tot,tmp[pos++] = ques[posb++];
for(int i = a;i <= b;i++)ques[i] = tmp[i];
}
int n,m;
int main(){
scanf("%d %d",&n,&m);
for(int x,y,i = 1;i <= n;i++)
scanf("%d %d",&x,&y),ques[++ques_tot] = Ques{x,y,1,0};
for(int a,b,c,d,i = 1;i <= m;i++){//拆點
scanf("%d %d %d %d",&a,&b,&c,&d);
ques[++ques_tot] = Ques{c,d,0,++ans_tot};
ques[++ques_tot] = Ques{c,b - 1,0,++ans_tot};
ques[++ques_tot] = Ques{a - 1,d,0,++ans_tot};
ques[++ques_tot] = Ques{a - 1,b - 1,0,++ans_tot};
}
sort(ques + 1,ques + 1 + ques_tot);
cdq(1,ques_tot);
for(int i = 1;i + 3 <= ans_tot;i += 4)
printf("%d\n",ans[i] - ans[i + 1] - ans[i + 2] + ans[i + 3]);
return 0;
}
三維偏序
時間複雜度\(O(nlog^2n)\)
有n個元素,第\(i\)個元素有\(a_i,b_i,c_i\)三個屬性,設\(f(i)\)表示滿足\(a_j ≤ a_i\)且\(b_j≤b_i\)且\(c_j≤c_i\)且\(i≠j\)的數量。對於\(d∈[0,n)\),求\(f(i)=d\)的數量
先按每個元素的a排序,然後第二維用歸併排序,第三維用樹狀陣列(需要離散化)
結構體裡a,b,c是三維,w是重複的個數,f是\(f(i)\)的值
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
const int N = 1e5 + 10;
int c[N << 1], ans[N], n, b[N], q;
struct Stars{
int a,b,c,w,f;//a一維,b二維,c三維,w表示三個維度都相同的個數,f是三維都≤本身的個數
}e[N], t[N];
bool cmp(Stars a,Stars b){
if(a.a == b.a && a.b == b.b)return a.c < b.c;
if(a.a == b.a)return a.b < b.b;
return a.a < b.a;
}
int lowbit(int x){
return x & (-x);
}
void add(int i,int k){
for(; i <= q; i += lowbit(i))c[i] += k;
}
int sum(int i){
int ans = 0;
for(; i; i -= lowbit(i))ans += c[i];
return ans;
}
void CDQ(int l,int r){
if(l == r)return;
int mid = (l + r) >> 1;
CDQ(l, mid);CDQ(mid + 1, r);
int p = l, q = mid + 1,tot = l;
while(p <= mid && q <= r){
if(e[p].b <= e[q].b)add(e[p].c,e[p].w),t[tot++] = e[p++];
else e[q].f += sum(e[q].c),t[tot++] = e[q++];//[l,p]的第二維比q的第二維小
}
while(p <= mid)add(e[p].c,e[p].w),t[tot++] = e[p++];
while(q <= r)e[q].f += sum(e[q].c),t[tot++] = e[q++];//[l,mid]的第二維都比q的第二維小
for(int i = l; i <= mid; i++)add(e[i].c, -e[i].w);//清空陣列陣列
for(int i = l; i <= r; i++)e[i] = t[i];
}
int main(){
scanf("%d",&n);
for(int i = 1; i <= n; i++)
scanf("%d%d%d",&e[i].a,&e[i].b,&e[i].c),e[i].w = 1, b[i] = e[i].c;
sort(b + 1, b + n + 1);
q = unique(b + 1, b + n + 1) - b - 1;
for(int i = 1; i <= n; i++)
e[i].c = lower_bound(b + 1, b + q + 1, e[i].c) - b;
sort(e + 1,e + n + 1,cmp);
int cnt = 1;
for(int i = 2; i <= n; i++){
if(e[i].a == e[cnt].a && e[i].b == e[cnt].b && e[i].c == e[cnt].c)e[cnt].w++;
else e[++cnt] = e[i];
}
CDQ(1, cnt);
for(int i = 1; i <= cnt; i++)ans[e[i].f + e[i].w - 1] += e[i].w;//e[i]的答案就是≤自己的個數+與自己值相同的個數-1(本身)
for(int i = 0; i < n; i++)printf("%d\n", ans[i]);
return 0;
}
四維偏序
時間複雜度\(O(nlog^3n)\)
\(CDQ\)套\(CDQ\)套樹狀陣列