cdq分治学习笔记

简介

cdq 分治通过分治的思想可以解决如下问题:

总的来说,cdq 分治可以通过分治去除一维的限制,因此广泛被用于多维偏序或转移方程有特定限制的 dp 之中。

一.解决和点对有关的问题

这种问题的通常表述:给定长度为 \(n\) 的序列,多次询问满足一些条件的点对 \((i,j)\) 的数量。

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

以这道题为例,我们需要统计所有 \(a_j\le a_i,b_j\le b_i,c_j\le c_i\) 的点对 \((i,j)\) 的数量。

显然可以对任意一维排序,这样问题就转化为二维偏序。

考虑分治求区间 \([l,r]\) 的贡献。

先递归至 \([l,mid]\)\([mid+1,r]\)

如果我们知道了这两个区间的答案,怎么求 \([l,r]\) 就是我们要解决的问题。

这里考虑贡献如何产生。

首先,我们显然知道对于任何序列里的 \(i,j\),如果 \(i<j\) ,那么 \(a_i<a_j\)(因为已经对原数组排序并且去重)。

那么同理,我们也可以将这两段区间按照 b 数组的大小排序。

然后现在我们把所有满足 \(b_i<b_j\) 的数丢进一个数据结构(注意 \(i\)\([l,mid]\) 中, \(j\)\([mid+1,r]\) 中),然后只需要看有多少个数对满足 \(c_i<c_j\) 即可。

可以发现是一个单点修改区间查询,使用树状数组维护即可。

但是我们发现如果枚举每一个 \(b_i<b_j\)\((i,j)\)\(O(\text{length}^2)\) 的(length 为区间长度) ,不可接受。

因为已经排序过,所以序列具有单调性,考虑使用双指针的思想,这样只需要 \(O(\text{length})\) 就可以统计完答案。

这样我们就在 \(O(\text{length} \log \text{length})\) 的复杂度内解决了一个长为 \(\text{length}\) 的区间的查询。

复杂度为 \(O(n \log ^2 n)\)

甚至可以 cdq 套 cdq 来做,但是码量变大而且不够优美。

CODE

#include <bits/stdc++.h>
using namespace std;
#define lowbit(x) (x&(-x))
const int maxn=100010;

struct node {
	int a,b,c,cnt,ans;
	friend bool operator !=(node x, node y) {
		return x.a!=y.a || x.b!=y.b || x.c!=y.c;
	}
} r[maxn],a[maxn];
bool cmp1(node x, node y) {
	if(x.a!=y.a) return x.a<y.a;
	if(x.b!=y.b) return x.b<y.b;
	return x.c<y.c;
}
bool cmp2(node x, node y) {
	return x.b==y.b?x.c<y.c:x.b<y.b;
}
int n,m;
int res[maxn];

//树状数组 
int t[maxn<<1],nt;//BIT
inline void update(int x,int o) {
	while(x<=nt) t[x]+=o,x+=lowbit(x);
	return ;
}
inline int query(int x) {
	int ans=0;
	while(x) ans+=t[x],x-=lowbit(x);
	return ans;
}

void cdq(int l,int r) {
	if(l==r) return ;
	int mid=(l+r)/2;
	cdq(l,mid),cdq(mid+1,r);
	sort(a+l,a+mid+1,cmp2);
	sort(a+mid+1,a+r+1,cmp2);
	int i=l,j=mid+1;//双指针,i 指向左半区间, j 指向右半区间
	while(j<=r) {
		while(i<=mid && a[i].b<=a[j].b) update(a[i].c,a[i].cnt),i++;
		a[j].ans+=query(a[j].c);//树状数组统计逆序对
		j++;
	}
	for(int k=l; k<i; k++) update(a[k].c,-a[k].cnt);//树状数组 
}
#undef mid

int main() {
	ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
	cin>>n>>nt;
	for(int i=1; i<=n; i++) cin>>r[i].a>>r[i].b>>r[i].c;//读入 
	sort(r+1,r+n+1,cmp1);
	int t=0; 
	for(int i=1; i<=n; i++) {
		t++;
		if(r[i]!=r[i+1]) a[++m].a=r[i].a,a[m].b=r[i].b,a[m].c=r[i].c,a[m].cnt=t,t=0;//需要去重 
	}
	cdq(1,m);//cdq分治 
	for(int i=1; i<=m; i++) res[a[i].ans+a[i].cnt-1]+=a[i].cnt;//统计答案 
	for(int i=0; i<n; i++) cout<<res[i]<<'\n'; 
	return 0;
} 

可以发现 cdq 分治 多么好写 ——【数据删除】

2.动态逆序对

先算一次初始逆序对,再算出每次减小时会减去多少逆序对数量即可。

考虑使用 cdq分治统计。

不难发现满足条件的点对 \((i,j)\) 都满足 \(\text{time}_i<\text{time}_j,\text{val}_i>\text{val}_j,\text{pos}_i<\text{pos}_j\)(pos 是位置)或者 \(\text{time}_i<\text{time}_j,\text{val}_i<\text{val}_j,\text{pos}_i>\text{pos}_j\),三维偏序。

CODE

#include <bits/stdc++.h>
using namespace std;
#define lowbit(x) (x&(-x))
const int maxn=100010;
#define int long long
struct node {
    int bl,val,pos,tim;
    friend bool operator < (node a, node b) {
        return a.pos<b.pos;
    }
} a[maxn<<1];
int n,m,pos[maxn],cnt;
int res[maxn];
int t[maxn<<1];
inline void update(int x,int o) {
    while(x<=n) t[x]+=o,x+=lowbit(x);
    return ;
}
inline int query(int x) {
    int ans=0;
    while(x) ans+=t[x],x-=lowbit(x);
    return ans;
}
#define mid ((l+r)>>1) 
void cdq(int l,int r) {
    if(l==r) return ;
    cdq(l,mid),cdq(mid+1,r);
    sort(a+l,a+mid+1);
    sort(a+mid+1,a+r+1);
    int i=l,j=mid+1;
    while(j<=r) {
        while(i<=mid && a[i].pos<=a[j].pos) update(a[i].val,a[i].bl),i++;
        res[a[j].tim]+=a[j].bl*(query(n)-query(a[j].val));
        j++;
    }
    for(int k=l; k<i; k++) update(a[k].val,-a[k].bl);
    i=mid,j=r;
    while(j>mid) {
        while(i>=l && a[i].pos>=a[j].pos) update(a[i].val,a[i].bl),i--;
        res[a[j].tim]+=a[j].bl*query(a[j].val-1),j--;
    }
    for(int k=mid; k>i; k--) update(a[k].val,-a[k].bl);
}
char ch;
inline int read(int x=0) {
    ch=getchar();
    while(!isdigit(ch)) ch=getchar();
    while(isdigit(ch)) x=x*10+ch-'0',ch=getchar();
    return x;
}
signed main() {
    n=read(),m=read();
    int x;
    for(int i=1; i<=n; i++) x=read(),pos[x]=i,a[++cnt]=(node){1,x,i,0};
    for(int i=1; i<=m; i++) x=read(),a[++cnt]=(node){-1,x,pos[x],i};
    cdq(1,cnt);
    for(int i=1; i<=m; i++) res[i]+=res[i-1];
    for(int i=0; i<m; i++) cout<<res[i]<<'\n'; 
    return 0;
} 
posted @ 2024-02-29 10:47  lgh_2009  阅读(10)  评论(0编辑  收藏  举报