CDQ分治·学习笔记
CDQ分治学习笔记
咕了好久,以前做过的好几道题都有关CDQ分治,但是都就抄了抄题解就扔了,没有怎么系统学过,今天学一下
CQD分治简介
二维偏序
众所周之,分治有三个基本的步骤:划分子问题;解决问题;合并答案
归并排序事分治
任务1 分治的一道经典题是求逆序对个数(P1908)
解:在归并排序的同时,处理答案(先排序再处理),从小到大排,当左指针的值>右指针的值时,代表分治的左区间中左指针右边的数都比右指针的值大,这就是逆序对啊,\(ans+=(mid-t1+1)\)
码:
int op[N],n;
void merge(int l,int r){
int res[N];
if(l==r) return;
int mid=(l+r)>>1;
merge(l,mid);
merge(mid+1,r);
int t1=l,t2=mid+1;
for(int i=l;i<=r;i++){
if((t1<=mid&&op[t1]<=op[t2])||t2>r){
res[i]=op[t1];
t1++;
}else{
res[i]=op[t2];
ans+=(mid-t1+1);
t2++;
}
}
for(int i=l;i<=r;i++) op[i]=res[i];
return;
}
这也叫做著名的二维偏序:
给定\(N\)个有序对\((a,b)\),求对于每个\((a,b)\),满足\(A<a\)且\(B<b\)的有序对\((A,B)\)有多少个。
这其实就是求顺序对,和上面一样
这就叫CDQ分治
二维偏序的扩展
任务2 找出来树状数组的板子题,拿二维偏序写一写试试(P3374)
解:把操作的时间看作a,把操作的位置看作b,a是有序的,对b进行cdq分治,求的是区间内操作对答案的贡献,但是⚠️注意:左区间的操作会对右区间的答案有影响,所以我们在分治的时候,左区间只处理修改,右区间只处理查询
码:
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<string>
#include<cmath>
#include<vector>
#include<map>
#include<queue>
#include<deque>
#include<set>
#include<stack>
#include<bitset>
#include<cstring>
#define ll long long
#define max(a,b) ((a>b)?a:b)
#define min(a,b) ((a<b)?a:b)
using namespace std;
const int INF=0x3f3f3f3f,N=5000010;
int n,m,cntx=0,cnt=0;
struct node{
int type,id;
ll val;
bool operator <(const node &a) const{
if(id!=a.id) return id<a.id;
else return type<a.type;
}
}a[N],b[N];
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);
int t1=l,t2=mid+1;
ll sum=0;
for(int i=l;i<=r;i++){
if((t1<=mid&&a[t1]<a[t2])||t2>r){
if(a[t1].type==1) sum+=a[t1].val;
b[i]=a[t1++];
}else{
if(a[t2].type==3) ans[a[t2].val]+=sum;
else if(a[t2].type==2) ans[a[t2].val]-=sum;
b[i]=a[t2++];
}
}
for(int i=l;i<=r;i++) a[i]=b[i];
}
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++){
cnt++;
a[cnt].type=1;
a[cnt].id=i;
scanf("%lld",&a[cnt].val);
}
for(int i=1;i<=m;i++){
int t;
scanf("%d",&t);
cnt++;
a[cnt].type=t;
if(t==1) scanf("%d%lld",&a[cnt].id,&a[cnt].val);
else {
int l,r;
scanf("%d%d",&l,&r);
cntx++;
a[cnt].val=cntx;
a[cnt].id=l-1;//前端点
cnt++;
a[cnt].type=3;
a[cnt].val=cntx;
a[cnt].id=r;//后端点
}
}
cdq(1,cnt);
for(int i=1;i<=cntx;i++) printf("%lld\n",ans[i]);
return 0;
}
三维偏序
任务3 (P3810)
给定\(N\)个有序三元组\((a,b,c)\),求对于每个三元组\((a,b,c)\),有多少个三元组\((A,B,C)\)满足\(A < a\)且\(B < b\)且\(C < c\)
解:最好的办法就是CDQ分治+数据结构,考虑可以比较权值大小的数据结构,可以用树状数组记录第三维,在保证序列前两维单调不下降的情况下,用代表权值的树状数组来处理答案
码:
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<string>
#include<cmath>
#include<vector>
#include<map>
#include<queue>
#include<deque>
#include<set>
#include<stack>
#include<bitset>
#include<cstring>
#define ll long long
#define max(a,b) ((a>b)?a:b)
#define min(a,b) ((a<b)?a:b)
using namespace std;
const int INF=0x3f3f3f3f,N=200005;
struct node{
int a,b,c,cnt,ans;
}s1[N],s2[N];
int n,m,k,mx,top,su[N];
int t[N];
//用于第一次排序去重
bool cmp1(node x,node y){
if(x.a==y.a){
if(x.b==y.b) return x.c<y.c;
else return x.b<y.b;
}else return x.a<y.a;
}
//用于区间内排序,排bc,保证区间内b维单调不下降
bool cmp2(node x,node y){
if(x.b==y.b) return x.c<y.c;
else return x.b<y.b;
}
//树状数组
int lowbit(int x){return x&(-x);}
void add(int x,int y){
while(x<=mx){
t[x]+=y;
x+=lowbit(x);
}
}
int query(int x){
int sum=0;
while(x){
sum+=t[x];
x-=lowbit(x);
}
return sum;
}
//CDQ分治
void cdq(int l,int r){
if(l==r) return;
int mid=(l+r)>>1;
cdq(l,mid);
cdq(mid+1,r);
sort(s2+l,s2+mid+1,cmp2);
sort(s2+1+mid,s2+r+1,cmp2);
int i,j=l;
//双指针扫两个区间
for(i=mid+1;i<=r;i++){
while(s2[i].b>=s2[j].b&&j<=mid){
add(s2[j].c,s2[j].cnt);
j++;
}
s2[i].ans+=query(s2[i].c);
}
//清空树状数组
for(i=l;i<j;i++) add(s2[i].c,-s2[i].cnt);
}
int main(){
scanf("%d%d",&n,&k);
mx=k;
for(int i=1;i<=n;i++){
int a,b,c;
scanf("%d%d%d",&a,&b,&c);
s1[i].a=a;
s1[i].b=b;
s1[i].c=c;
}
sort(s1+1,s1+1+n,cmp1);
for(int i=1;i<=n;i++){
top++;
if(s1[i].a!=s1[i+1].a||s1[i].b!=s1[i+1].b||s1[i].c!=s1[i+1].c){
m++;
s2[m].a=s1[i].a;
s2[m].b=s1[i].b;
s2[m].c=s1[i].c;
s2[m].cnt=top;
top=0;
}
}
cdq(1,m);
for(int i=1;i<=m;i++) su[s2[i].ans+s2[i].cnt-1]+=s2[i].cnt;
for(int i=0;i<n;i++) printf("%d\n",su[i]);
return 0;
}
CDQ分治例题
lj给的题
任务4 BZOJ2001
CDQ分治+并查集
任务5 BZOJ2244
CDQ分治+dp
任务6 BZOJ2989
CDQ分治+数据结构
任务7 BZOJ1942
CDQ分治+斜率优化dp
有时间再写