CDQ学习笔记

想说的话

其实我也不是很会,写这样一篇笔记送给未来的自己

-----------------------------------------------------------------------------(NOIP2020)前一天,(xxbbkk)

算法思想

(CDQ)分治是分治的一种,本着分而治之的思想,用一半来算另一半的贡献。可以解决偏序问题。(蒟蒻只会三维以下)。

例题一:逆序对问题

用树状数组或归并排序的思想就可以做,是一个二维偏序问题。用权值树状数组。

这里是归并的。

#include<bits/stdc++.h>
using namespace std;
int n,a[500010],ans,a1[500010],b1[500010];
inline void msort(int l,int r){
	if(l==r) return;
	int mid=(l+r)>>1;
	msort(l,mid),msort(mid+1,r);
	for(int i=l;i<=mid;i++) a1[i]=a[i];
	for(int i=mid+1;i<=r;i++) b1[i]=a[i];
	int tot=l-1,l1=l,l2=mid+1;
	while(l1<=mid&&l2<=r){
		if(a1[l1]>b1[l2]){
			a[++tot]=b1[l2];
			ans+=mid-l1+1;
			l2++;
		}
		else{
			a[++tot]=a1[l1];
			l1++;
		}
	}
	while(l1<=mid) a[++tot]=a1[l1++];
	while(l2<=r) a[++tot]=b1[l2++];
	return;
}
int main(){
	scanf("%d",&n);
	for(int i=1;i<=n;i++) scanf("%d",&a[i]);
	msort(1,n);
	printf("%d
",ans);
	return 0;
}

这里是两种方法选其一,而三维偏序就是把他们结合起来。

例题二:三维偏序问题

Lougu[P3810]

先以(a)为第一关键字排序,解决掉一维。

向下分治时以(b)为第二关键字排序,再解决掉一维。在这里算的时候,我们只算前面那一半与后面那一半的组合产生的贡献,也就是跨越中点的贡献,这是分治的基本原则。

计算时用两个指针(l_1,l_2)(l_1)遍历前面的一半,(l_2)遍历后面的一半,此时我们保证了前边一半的(a)小于后面一半的(a),而两半内部的(b)是有序的,所以只要后一半的位置(l_2)(b_{l_2})刚好大于前一半的某个位置(l_1)(b_{l_1}),就可以把(l_1)之前所有的(c)的值加入权值树状数组中,在权值树状数组中查询(c_{l_2})的前缀和。

这里看代码清楚些。

inline void CDQ(int l,int r){
	if(l==r) return;
	int mid=(l+r)>>1;
	CDQ(l,mid),CDQ(mid+1,r);
	sort(v+l,v+mid+1,cmp2),sort(v+mid+1,v+r+1,cmp2);
	int l1=l,l2;
	for(l2=mid+1;l2<=r;l2++){
      //移动指针l1,找到那个刚好大于的位置pos1,并在权值线段中插入遍历到的c
		while(v[l1].b<=v[l2].b&&l1<=mid){
			add(v[l1].c,v[l1].cnt);
			l1++;
		}
      //累加答案,把权值树状数组中小于(此时的c_l2)的数的个数加上
		v[l2].ans+=sum(v[l2].c);
	}
  //消除这一层的改变,还原树状数组
	for(l2=l;l2<l1;l2++) add(v[l2].c,-v[l2].cnt);
	return;
}

还有,如果有完全相同的一组(a,b,c), 就有可能算不到贡献。

所以我们要去重,统计个数。

code

#include<bits/stdc++.h>
#define N (100010)
using namespace std;
struct xbk{int a,b,c,cnt,ans;}x[N],v[N];
int n,m,k,c[200010],fa[200010];
inline int read(){
	int w=0;
	char ch=getchar();
	while(ch>'9'||ch<'0') ch=getchar();
	while(ch>='0'&&ch<='9'){
		w=(w<<3)+(w<<1)+(ch^48);
		ch=getchar();
	}
	return w;
}
inline int lb(int x){return x&(-x);}
inline bool cmp1(xbk aa,xbk bb){
	if(aa.a!=bb.a) return aa.a<bb.a;
	else if(aa.b!=bb.b) return aa.b<bb.b;
	else return aa.c<bb.c;
}
inline bool cmp2(xbk aa,xbk bb){
	if(aa.b!=bb.b) return aa.b<bb.b;
	else return aa.c<bb.c;
}
inline void add(int x,int g){
	for(int i=x;i<=k;i+=lb(i)) c[i]+=g;
}
inline int sum(int x){
	int res=0;
	for(int i=x;i;i-=lb(i)) res+=c[i];
	return res;
}
inline void pre(){
	int now=0;
	sort(x+1,x+1+n,cmp1);
	for(int i=1;i<=n;i++){
		now++;
		if(x[i].a!=x[i+1].a||x[i].b!=x[i+1].b||x[i].c!=x[i+1].c){
			v[++m].a=x[i].a;
			v[m].b=x[i].b,v[m].c=x[i].c;
			v[m].ans=0,v[m].cnt=now;
			now=0;
		}
	}
	return;
}
inline void CDQ(int l,int r){
	if(l==r) return;
	int mid=(l+r)>>1;
	CDQ(l,mid),CDQ(mid+1,r);
	sort(v+l,v+mid+1,cmp2),sort(v+mid+1,v+r+1,cmp2);
	int l1=l,l2;
	for(l2=mid+1;l2<=r;l2++){
		while(v[l1].b<=v[l2].b&&l1<=mid){
			add(v[l1].c,v[l1].cnt);
			l1++;
		}
		v[l2].ans+=sum(v[l2].c);
	}
	for(l2=l;l2<l1;l2++) add(v[l2].c,-v[l2].cnt);
	return;
}
int main(){
	n=read(),k=read();
	for(int i=1;i<=n;i++)
		x[i].a=read(),x[i].b=read(),x[i].c=read();
	pre();
	CDQ(1,m);
	for(int i=1;i<=m;i++) fa[v[i].ans+v[i].cnt-1]+=v[i].cnt;
	for(int i=0;i<n;i++) printf("%d
",fa[i]);
	return 0;
}

例题三:动态逆序对

LouguP3157

待修改的逆序对,用权值作第一维(a),位置作第二维(b),结束时间做第三维(c)

元素(y)对于元素(x)是逆序对的条件为:

(x.a<y.a)(x.b > y.b)(x.c>y.c)

或:

(x.a>x.b)(x.b<y.b)(x.c>y.c)

就变成两个三维偏序啦♪(*)

code

#include<bits/stdc++.h>
#define N (100010)
#define K (200010)
#define ll long long
using namespace std;
struct xbk{int a,b,c,cnt;ll ans;}x[N],v[N];
int n,m,kk,tot,cnt,now,c[K],re[N];
ll fi,ans[N];
inline int lb(int x){return x&(-x);}
inline int read(){
	int w=0;
	char ch=getchar();
	while(ch>'9'||ch<'0') ch=getchar();
	while(ch>='0'&&ch<='9'){
		w=(w<<3)+(w<<1)+(ch^48);
		ch=getchar();
	}
	return w;
}
inline void add(int x,int kkk){
	for(int i=x;i<=n;i+=lb(i)) c[i]+=kkk;
	return;
}
inline ll sum(int x){
	int res=0;
	for(int i=x;i>0;i-=lb(i)) res+=c[i];
	return res;
}
inline bool cmp1(xbk aa,xbk bb){
	if(aa.a!=bb.a) return aa.a>bb.a;
	else if(aa.b!=bb.b) return aa.b<bb.b;
	else return aa.c<bb.c;
}
inline bool cmp2(xbk aa,xbk bb){
	if(aa.b!=bb.b) return aa.b<bb.b;
	else return aa.c<bb.c;
}
inline bool cmp3(xbk aa,xbk bb){
	if(aa.b!=bb.b) return aa.b>bb.b;
	else return aa.c<bb.c;
}
inline void CDQ(int l,int r){
	if(l==r) return;
	int mid=(l+r)>>1;
	CDQ(l,mid),CDQ(mid+1,r);
	sort(v+l,v+mid+1,cmp3),sort(v+mid+1,v+r+1,cmp3);
	int i=l,j=mid+1;
	for(j=mid+1;j<=r;j++){
		while(v[i].b>v[j].b&&i<=mid){
			add(v[i].c,v[i].cnt);
			i++;
		}
		v[j].ans+=sum(v[j].c);
	}
	for(int k=l;k<i;k++) add(v[k].c,-v[k].cnt);
	sort(v+l,v+mid+1,cmp2),sort(v+mid+1,v+r+1,cmp2);
	i=l;
	for(j=mid+1;j<=r;j++){
		while(v[i].b<v[j].b&&i<=mid){
			add(v[i].c,v[i].cnt);
			i++;
		}
		v[j].ans+=sum(n)-sum(v[j].c);
	}
	for(int k=l;k<i;k++) add(v[k].c,-v[k].cnt);
	return;
}
int main(){
	n=read(),m=read();
	kk=n;
	for(int i=1;i<=n;i++){
		v[i].b=read(),v[i].c=i;
		re[v[i].b]=i;
		v[i].a=1e9,v[i].cnt=1,v[i].ans=0;
	}
	for(int i=1;i<=m;i++){
		int xx=read();
		v[re[xx]].a=i;
	}
	sort(v+1,v+1+n,cmp1);
	CDQ(1,n);
//	for(int i=1;i<=n;i++){
//		printf("%d %d %d %d
",v[i].a,v[i].b,v[i].c,v[i].ans);
//	}
	
	for(int i=1;i<=n;i++){
		fi+=v[i].ans;
		if(v[i].a!=1e9) ans[v[i].a]=v[i].ans;
	}
	for(int i=1;i<=m;i++) printf("%lld
",fi-=ans[i-1]);
	return 0;
}

未完待续❀

原文地址:https://www.cnblogs.com/xxbbkk/p/14086163.html