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

有时间再写

posted @ 2021-05-18 23:45  wsy_jim  阅读(94)  评论(0编辑  收藏  举报