CDQ分治

CDQ分治

什么是CDQ分治?

CDQ分治是一种基于时间(其实不一定)的分治,最基本的运用是三位偏序。

三位偏序问题 [BZOJ3262]陌上花开

对于这个问题,我们先对点去重。

以第一维(花形)为第一关键字,第二维(颜色)为第二关键字,第三维(气味)为第三关键字,从小到大将点排序。

分治,回溯时处理答案

回溯时,将两个子序列以第二维(颜色)为第一关键字,第三维(气味)为第二关键字分别排序

通过左子序列更新右子序列的答案,记录两个下标\(j~|~j\epsilon[left,mid]\)\(i~|~i\epsilon[mid+1,right]\),从左向右移动,使得点\(i\)的颜色值(第二维)始终比点\(j\)的大。

如此一来,点\(i\)的花形值(第一维)始终比点\(k~|~k\epsilon[left,j]\)的大,点\(i\)的颜色值(第二维)始终比点\(k~|~k\epsilon[left,j]\)的大,所以只用求\(\sum_{k=left}^{j}k_{第三维}≤i_{第三维}\)的值即可,显然可以通过建立树状数组来存储。

由于每次都是通过左子序列更新右子序列的答案,所以一定不会有遗漏

代码如下:

#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<vector>
#define maxn 100000
#define maxk 200000
#define INF 0x3f3f3f3f3f3f3f3f
using namespace std;
void read(int &x){
    int f=1;x=0;
    char ch=getchar();
    while(!isdigit(ch)){
        if(ch=='-') f=-1;
        ch=getchar();
    }
    while(isdigit(ch)){
        x=x*10+ch-'0';
        ch=getchar();
    }
    x*=f;
}
int n,k;
struct BIT{                                 //树状数组
    int tree[maxk+5];
    int lowbit(int x){return x&-x;}
    void update(int x,int v){
        for(int i=x;i<=k;i+=lowbit(i))
            tree[i]+=v;
    }
    int query(int x){
        int ans=0;
        for(int i=x;i>=1;i-=lowbit(i)) ans+=tree[i];
        return ans;
    }
} Tr;
struct node{
    int x,y,z,i,cnt,ans;
    node(){x=y=z=i=cnt=ans=0;}
    friend bool operator==(node a,node b){return a.x==b.x&&a.y==b.y&&a.z==b.z;}
    friend bool operator!=(node a,node b){return !(a==b);}
} A[maxn+5],w[maxn+5],s1[maxn+5];
int cnt=0;
bool cmp1(node a,node b){return a.x!=b.x?a.x<b.x:(a.y!=b.y?a.y<b.y:a.z<b.z);}
bool cmp2(node a,node b){return a.y!=b.y?a.y<b.y:a.z<b.z;}
void merge(node s[],int left,int right){
    int mid=(left+right)/2;
    int i=left,j=mid+1;
    for(int k=left;k<=right;k++){
        if(j>right||(i<=mid&&cmp2(s[i],s[j]))) s1[k]=s[i++];
        else s1[k]=s[j++];
    }
    for(int k=left;k<=right;k++) s[k]=s1[k];
}
void uniq(node s1[],node s2[],int cnt1,int &cnt2){
    cnt2=0;
    for(int i=1;i<=cnt1;i++){
        if(i==1||s1[i]!=s1[i-1]) s2[++cnt2]=s1[i];
        else s2[cnt2].cnt+=s1[i].cnt;
    }
}
void CDQ(int l,int r){
    if(l==r) return;
    int mid=(l+r)/2;
    CDQ(l,mid),CDQ(mid+1,r);
    merge(w,l,mid),merge(w,mid+1,r);        //归并排序
    int j=l-1;
    for(int i=mid+1;i<=r;i++){
        while(j<mid&&w[j+1].y<=w[i].y)
            j++,Tr.update(w[j].z,w[j].cnt);
        w[i].ans+=Tr.query(w[i].z);
    }
    for(int i=l;i<=j;i++)
        Tr.update(w[i].z,-w[i].cnt);
}
int Ans[maxn+5];
int main(){
    read(n),read(k);
    for(int i=1;i<=n;i++) read(A[i].x),read(A[i].y),read(A[i].z),A[i].i=i,A[i].cnt=1;
    sort(A+1,A+n+1,cmp1),uniq(A,w,n,cnt);   //去重
    CDQ(1,cnt),sort(w+1,w+cnt+1,cmp1);
    for(int i=1;i<=cnt;i++) Ans[w[i].ans+w[i].cnt-1]+=w[i].cnt;
    for(int i=0;i<n;i++) printf("%d\n",Ans[i]);
}

转载于:https://www.cnblogs.com/OIER-Yu/p/11438545.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值