CDQ 分治

CDQ 分治

在二维偏序问题中,可以利用排序干掉一维,并用树状数组维护另一维.

在三维偏序中,就需要利用 cdq 分治,干掉一维,并用树状数组+排序维护剩下两维.   

例题

1. 树状数组模板题  

本来,这道题应该用树状数组来完成,不过可以利用本题练习 $\mathrm{cdq}$ 分治.   

操作序列的顺序是一维,操作的位置是第二维.   

利用 $\mathrm{cdq}$ 解决掉第一维,再利用排序解决第二维.  

#include <cstdio>
#include <cstring>
#include <vector>
#include <algorithm>
#define N  500009 
#define ll long long 
#define pb push_back 
#define setIO(s) freopen(s".in","r",stdin)   
using namespace std;
int val[N], cnt, n, m, scc;    
struct data {
	int ty, pos, d;  
	data(int ty=0,int pos=0,int d=0):ty(ty),pos(pos),d(d){}  
	bool operator<(const data b) const {
		return pos == b.pos ? ty < b.ty : pos < b.pos; 
	}
}A[N * 3], tmp[N * 3];   
ll ans[N];  
void cdq(int l, int r) {
	if(l >= r) { 
		return ; 
	}
	int mid = (l + r) >> 1; 
	cdq(l, mid), cdq(mid + 1, r); 
	ll sum = 0ll;  
	int p = l, cn = 0, q = mid + 1;  
	while(p <= mid && q <= r) {
		if(A[p] < A[q]) {
			// 左面.  
			if(A[p].ty == 1) {
				sum += A[p].d;   
			}
			tmp[++cn] = A[p];  
			++ p;  
		}
		else {
			if(A[q].ty == 2) {
				ans[A[q].d] += sum;    
			}
			tmp[++cn] = A[q];  
			++ q; 
		}
	}
	while(p <= mid) {
		tmp[++cn] = A[p]; 
		p ++;  
	}
	while(q <= r) {
		if(A[q].ty == 2) ans[A[q].d] += sum; 
		tmp[++cn] = A[q];  
		q ++ ; 
	}
	for(int i = 1; i <= cn ; ++i) {
		A[l + i - 1] = tmp[i];  
	}
}
int main() {
	// setIO("input");  
	scanf("%d%d",&n,&m); 
	for(int i=1;i<=n;++i) {
		scanf("%d",&val[i]); 
	}
	for(int i=1;i<=m;++i) {
		int x, y, z, w, o;  
		scanf("%d", &o); 
		if(o == 1) {
			scanf("%d%d%d",&x,&y,&z);  
			++ cnt;  
			A[cnt].ty = 1;  
			A[cnt].pos = x; 
			A[cnt].d = z;  

			if(y + 1 <= n) {
				++ cnt; 
				A[cnt].ty = 1;  
				A[cnt].pos = y + 1;  
				A[cnt].d = -z; 
			}
		}
		else {
			++ cnt;  
			A[cnt].ty = 2;  
			scanf("%d",&A[cnt].pos); 
			A[cnt].d = ++ scc;  
			ans[scc] = val[A[cnt].pos];   
		}
	}
	cdq(1, cnt); 
	for(int i=1;i<=scc;++i) {
		printf("%lld\n", ans[i]); 
	}
	return 0; 
}

  

2.三维偏序(陌上花开)

利用之前提到的做法做就即可,用 $\mathrm{cdq}$ 分治干掉第一维,归并排序干掉第二维,树状数组搞第三维.  

#include <cstdio>
#include <cstring>
#include <vector>
#include <algorithm>
#define N  200009 
#define ll long long 
#define pb push_back 
#define setIO(s) freopen(s".in","r",stdin) 
using namespace std; 
struct BIT {
	int C[N];  
	int lowbit(int x) {
		return x & (-x); 
	}
	void upd(int x, int v) {
		while(x < N){
			C[x] += v; 
			x += lowbit(x); 
		}
	}
	int query(int x) {
		int tmp = 0; 
		while(x > 0) {
			tmp += C[x];
			x -= lowbit(x); 
		}
		return tmp ; 
	}
	void clr(int x) {
		while(x < N) {
			C[x] = 0; 
			x += lowbit(x); 
		}
	}
}T;
int n,K,cnt;  
ll ans[N], num[N];  
struct data {
	int x, y, z, k, id;   
	data(int x=0,int y=0,int z=0,int k=0,int id=0):x(x),y(y),z(z),k(k),id(id){} 
}a[N], A[N], tmp[N], F[N];   
bool cmp1(data i, data j) {
	if(i.x == j.x && i.y == j.y) return i.z < j.z;  
	if(i.x == j.x) return i.y < j.y;  
	return i.x < j.x;  
}  
void cdq(int l, int r) {
	if(l >= r) {
		return ;  
	}
	int mid = (l + r) >> 1;  
	cdq(l, mid), cdq(mid + 1, r);     
	// solve 完左面与右面.          
	int p = l, q = mid + 1, cn = 0;  
	while(p <= mid && q <= r) {
		if(A[p].y <= A[q].y) {
			T.upd(A[p].z, A[p].k);
			tmp[++cn] = A[p];     
			++ p;
		}
		else {
			ans[A[q].id] += T.query(A[q].z);  
			tmp[++cn] = A[q];  
			++ q; 
		}
	}
	while(p <= mid) {
		tmp[++cn] = A[p]; 
		++ p; 
	}
	while(q <= r) {
		ans[A[q].id] += T.query(A[q].z);  
		tmp[++cn] = A[q];  
		++q; 
	}       
	for(int i=1;i<=cn;++i) T.clr(tmp[i].z);  
	for(int i=1;i<=cn;++i) {
		A[l+i-1]=tmp[i]; 
	}
}
int main() {
	// setIO("input");  
	scanf("%d%d",&n,&K);
	for(int i=1;i<=n;++i) {
		scanf("%d%d%d",&a[i].x,&a[i].y,&a[i].z); 
	} 
	sort(a + 1, a + 1 + n, cmp1);  
	for(int i=1;i<=n;++i) {
		if((a[i].x != a[i-1].x)||(a[i].y!=a[i-1].y)||(a[i].z!=a[i-1].z)) {
			++ cnt;  
			A[cnt] = a[i];  
			A[cnt].k = 1;   
			A[cnt].id = cnt;  
		}
		else {
			++A[cnt].k;  
		}
	}      
	for(int i=1;i<=cnt;++i) {
		ans[i] = A[i].k - 1;     
		F[i] = A[i];   
	}     
	cdq(1, cnt);   
	for(int i=1;i<=cnt;++i) {
		num[(int)ans[i]] += F[i].k ;                   
	}      
	for(int i=0;i<n;++i) {
		printf("%lld\n", num[i]); 
	}
	return 0; 
}

  

3.动态逆序对

来源:luogu3157  

可以先求出初始情况逆序对,然后考虑每次删除所产生的贡献.  

每次删除的贡献可以写成是一个三维数点的形式,这个就可以用 $\mathrm{CDQ}$ 分治维护了. 

1. 操作顺序:最开始排个序就行. 

2. 每个数字的位置:在 $\mathrm{CDQ}$ 分治时归并排序合并好. 

3. 每个数字大小:利用树状数组来维护.  

这样,三个维度就都被维护好了.  

#include <cstdio>
#include <cstring>
#include <vector>
#include <algorithm>
#define N  100009 
#define ll long long 
#define pb push_back 
#define setIO(s) freopen(s".in","r",stdin) 
using namespace std; 
struct BIT {
    int C[N]; 
    int lowbit(int x) {
        return x & (-x); 
    }
    void upd(int x, int v) {
        while(x < N) {
            C[x] += v; 
            x += lowbit(x); 
        }
    }
    int query(int x) {
        int tmp = 0; 
        while(x > 0) {
            tmp += C[x]; 
            x -= lowbit(x); 
        }
        return tmp; 
    }
    void clr(int x) {
        while(x < N) {
            C[x] = 0; 
            x += lowbit(x); 
        }
    }
}T; 
struct data {
    int t, pos, v; 
    data(int t=0,int pos=0,int v=0):t(t),pos(pos),v(v){}  
}A[N << 1], tmp[N << 1]; 
int val[N], pos[N], del[N], det[N], n, m;    
// 直接利用 t 贡献就行.   
void cdq(int l, int r) {
    if(l >= r) {
        return ; 
    }
    int mid = (l + r) >> 1;  
    cdq(l, mid), cdq(mid + 1, r); 
    // 按照操作顺序已经排好第一维了.  
    // 根据操作顺序,应该是大影响小的.  
    int cur = mid + 1;
    for(int i = l; i <= mid ; ++ i) {
        while(cur <= r && A[cur].pos < A[i].pos) {
            T.upd(A[cur].v, 1);  
            ++ cur;  
        }
        det[A[i].t] += T.query(n) - T.query(A[i].v);  
    }
    for(int i = mid + 1; i <= r; ++ i) T.clr(A[i].v);   
    cur = r; 
    for(int i = mid; i >= l; -- i) {
        while(cur >= mid + 1 && A[cur].pos > A[i].pos) {
            T.upd(A[cur].v, 1); 
            -- cur; 
        }
        det[A[i].t] += T.query(A[i].v - 1);   
    }
    for(int i = mid + 1; i <= r; ++ i) T.clr(A[i].v);    
    // 最后需要归并一下(按照pos排序)  
    int p0 = l, q0 = mid + 1, o = 0;  
    while(p0 <= mid && q0 <= r) {
        if(A[p0].pos < A[q0].pos) {
            tmp[++o] = A[p0];  
            ++p0; 
        }
        else {
            tmp[++o] = A[q0]; 
            ++q0; 
        }
    }
    while(p0 <= mid) tmp[++o] = A[p0], ++p0; 
    while(q0 <= mid) tmp[++o] = A[q0], ++q0;  
    for(int i = 1; i <= o; ++ i) {
        A[l + i - 1] = tmp[i];  
    }
}
int main() {
    // setIO("input"); 
    scanf("%d%d",&n,&m); 
    ll sum = 0; 
    for(int i=1;i<=n;++i) {
        scanf("%d",&val[i]); 
        pos[val[i]] = i; 
        sum += T.query(n) - T.query(val[i]);  
        T.upd(val[i], 1);  
    }
    for(int i = 1; i <= n ; ++ i) T.clr(i); 
    for(int i=1;i<=n;++i) {   
        del[i] = n + 1; 
    }
    int cnt = m; 
    for(int i=1;i<=m;++i) {
        int x; 
        scanf("%d",&x);  
        del[x] = i; 
        A[i].t = i;  
        A[i].pos = pos[x]; 
        A[i].v = x; 
    }  
    for(int i=1;i<=n;++i) {
        if(del[i] > n) {
            ++cnt; 
            A[cnt].t = del[i]; 
            A[cnt].pos = pos[i]; 
            A[cnt].v = i;  
        }
    } 
    cdq(1, cnt); 
    printf("%lld\n",sum); 
    for(int i=1;i<m;++i) {
        sum -= det[i];  
        printf("%lld\n", sum); 
    }
    return 0; 
}

  

posted @ 2021-10-28 23:29  guangheli  阅读(71)  评论(0)    收藏  举报