CDQ分治学习笔记

本文介绍了CDQ分治算法的基本概念及其应用场景,包括如何结合树状数组解决三维偏序问题,以及如何应用于动态维护凸包等问题。通过具体的代码示例展示了算法的实现细节。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

今天学了一下cdq分治,感觉这东西真的挺好用的,赶紧写点东西怕以后再忘咯
其实类似于cdq分治的东西在oi早期学排序的时候就应该学过,那就是归并排序;
归并排序的原理和cdq分治大体一样,先划分成两个区间,递归解决两边,再合并起来;
并且用归并排序求逆序对的时候本质上就是在解决一个二维偏序的问题;
首先回忆一下归并排序是怎么求逆序对的;
(我太弱了不大好描述。。。归并排序相关网上找找就好了);
这其中就利用了已经排好序的一半区间来更新另一半区间的答案,这就是cdq分治的主要思想;
当要解决的问题有时间先后时(例如 dp,n维偏序),并且后边的答案不会影响前边的答案,就可以采用cdq分治;
有一类很经典的三维偏序问题,首先可以考虑树套树解决,但那样太难写;
在求逆序对的时候有一种树状数组做法,同样有很优秀的时空复杂度,所以考虑将cdq分治和树状数组结合起来;
cdq分治中数列的顺序是第一维,每个位置上的值是第二维,再将每个位置上加一个数,其在权值树状数组中的先后顺序是第三维;
当前两维都满足条件时,就调用树状数组看第三维是否满足;
然后归并的时候将第二维上的值按顺序排好,保证更大的区间合并时两边的小区间的前两维已经有序,这样在cdq分治nlog的基础上在加一个log就可以了;
还有一种cdq分治的应用是动态维护凸包;
一般能用单调队列的斜率优化dp题,必须要都满足每个点的横坐标单调并且查找的斜率单调,当其中一个条件不满足时就能用cdq分治解决,而两个条件都不满足时要多个二分,时间上多个log;
当一个条件不满足时,先递归处理其中一半区间,算出那一半区间的值后再归并排序将不满足的条件排好序使其满足单调;
例如sdoi2012任务安排;
此题每个点横坐标因为出题人鬼畜而强行不单调;
因为dp是无后效性的,所以处理完dp值后随便你怎么排序都无所谓;
所以处理完一半的值后,顺带排好序,由于另一半还没有动,斜率单调,可以线性求出另一半如果从前一半转移值是多少,再递归处理另一半(本来想用左右区间描述的。。。但这题很鬼畜从后向前转移)
yy 了一下,如果只是斜率不单调,大概可以先递归排序好一段区间的斜率再从前边斜率是乱的但横坐标是有序的能够线性求凸包那里进行转移;

把今天做的几道题的代码贴一下吧orz

bzoj 2726 sdoi2012任务安排 cdq维护凸包

#include<iostream>
#include<algorithm>
#include<string>
#include<cstring>
#include<cstdio>
#define LL long long
#define random(a,b) (a+rand()%((b)-(a)+1))
const int maxn=300005;
int n;
LL dp[maxn],f[maxn],t[maxn],s;
int id[maxn];
int tp[maxn];
int que[maxn];
double X(int x){
    return (double)t[x]; 
}
double Y(int x){
    return (double)dp[x];
}
bool check(int x,int y,int z)
{
    return (X(x)-X(y))*(Y(y)-Y(z))-(Y(x)-Y(y))*(X(y)-X(z))<0.0;  
}
double getval(int i,int j){
    return  (double)dp[j]+(double)f[i]*t[i]+(double)f[i]*s-(double)f[i]*t[j];
} 
bool vs(int x,int y,int i)
{
    return getval(i,x)-getval(i,y)<0.0;  
} 
void CDQ(int l,int r)
{
    if(l==r)return;
    int mid=(l+r)>>1;
    CDQ(mid+1,r);
    int tail=0,head=0;
    for(int i=mid+1;i<=r;i++){
        for(;tail-1>head;tail--)
            if(check(id[i],que[tail-1],que[tail-2]))break; 
        que[tail++]=id[i];
    }
    for(int k=mid;k>=l;k--){
        for(;head<tail-1;head++)
            if(vs(que[head],que[head+1],id[k]))break;
        int i=id[k];
        int j=que[head];
        dp[i]=std::min(dp[i],dp[j]+f[i]*t[i]+f[i]*s-f[i]*t[j]);
    }
    CDQ(l,mid);
    int p1=l,p2=mid+1;
    for(int i=l;i<=r;i++)
        if(p1>mid)tp[i]=id[p2++];
        else if(p2>r)tp[i]=id[p1++];
        else {
            if(t[id[p1]]<t[id[p2]])tp[i]=id[p1++];
            else tp[i]=id[p2++];
        }
    for(int i=l;i<=r;i++)
        id[i]=tp[i];
}
int main()
{
    scanf("%d%lld",&n,&s);
    for(int i=1;i<=n;i++)scanf("%lld%lld",&t[i],&f[i]);
    for(int i=n;i>=1;i--)t[i]+=t[i+1],f[i]+=f[i+1],dp[i]=f[i]*(t[i]+s),id[i]=i;
    CDQ(1,n);
    printf("%lld\n",dp[1]);
    return 0;
}

bzoj 2683 简单题 三维偏序

#include<iostream>
#include<algorithm>
#include<string>
#include<cstring>
#include<cstdio>
#define LL long long
#define random(a,b) (a+rand()%((b)-(a)+1))
int n;
const int maxn=200005;
struct asd
{
    int x,y,check,val;  
    bool operator <(const asd&p)const{
        if(x!=p.x)return x<p.x;
        else return y<=p.y;
    }
}q[maxn*4];
int anstot=0;
int xa,ya,xb,yb;
int ans[maxn];
asd tp[maxn*4];
int a[maxn*3];
int lowbit(int x)
{
    return x&(-x);
}
void add(int pos,int v)
{
    for(int i=pos;i<=n;i+=lowbit(i))a[i]+=v;
}
int query(int pos)
{
    int tmpans=0;
    for(int i=pos;i;i-=lowbit(i))tmpans+=a[i];
    return tmpans;
}
void CDQ(int l,int r)
{
    if(l==r)return;
    int mid=(l+r)>>1;
    CDQ(l,mid);
    CDQ(mid+1,r);
    int p1=l,p2=mid+1,num=0;
    while(p1<=mid&&p2<=r){
        if(q[p1]<q[p2]){
            if(q[p1].check==1)
                add(q[p1].y,q[p1].val);
            tp[++num]=q[p1++];
        }
        else{
            if(q[p2].check!=1){
                int tans=query(q[p2].y);
                ans[q[p2].val]+=tans*(q[p2].check==2 ? 1 : -1);
            }
            tp[++num]=q[p2++];
        }
    }
    while(p1<=mid){
        if(q[p1].check==1)
            add(q[p1].y,q[p1].val);
        tp[++num]=q[p1++];
    }
    while(p2<=r){
        if(q[p2].check!=1){
            int tans=query(q[p2].y);
            ans[q[p2].val]+=tans*(q[p2].check==2 ? 1 : -1);
        }
        tp[++num]=q[p2++];
    }
    for(int i=l;i<=mid;i++){
        if(q[i].check==1)
            add(q[i].y,-q[i].val);
    }
    int tpn=0;
    for(int i=l;i<=r;i++){
        q[i]=tp[++tpn];
    }   
} 
int main()
{
    scanf("%d",&n);
    int tot=0;
    for(tot=1;;tot++)
    {
        scanf("%d",&q[tot].check);
        if(q[tot].check==1){
            scanf("%d%d%d",&q[tot].x,&q[tot].y,&q[tot].val);
        }
        else if (q[tot].check==2){
            anstot++;
            scanf("%d%d%d%d",&xa,&ya,&xb,&yb);
            q[tot].x=xb,q[tot].y=yb,q[tot].val=anstot;
            q[++tot].x=xa-1,q[tot].y=yb,q[tot].check=3,q[tot].val=anstot;
            q[++tot].x=xb,q[tot].y=ya-1,q[tot].check=3,q[tot].val=anstot;
            q[++tot].x=xa-1,q[tot].y=ya-1,q[tot].check=2,q[tot].val=anstot;
        }
        else break;
    }
    tot--;
    CDQ(1,tot);
    for(int i=1;i<=anstot;i++)printf("%d\n",ans[i]);
    return 0;
}

bzoj 3262 陌上花开 三维偏序

#include<iostream>
#include<algorithm>
#include<string>
#include<cstring>
#include<cstdio>
#define LL long long
#define random(a,b) (a+rand()%((b)-(a)+1))
const int maxn=100005;
struct asd{
    int si,ci,mi;
    int ans;
    bool operator < (const asd &p)const{
        if(ci!=p.ci)return ci<p.ci;
        else return mi<=p.mi;
    }
    bool operator ==(const asd &p)const{
        return si==p.si&&ci==p.ci&&mi==p.mi;
    } 
}q[maxn],tp[maxn];
int n,k,rans[maxn];
int a[maxn*2];
int lowbit(int x)
{
    return x&(-x);
} 
void add(int pos,int v)
{
    for(int i=pos;i<=k;i+=lowbit(i))a[i]+=v;
}
int query(int pos)
{
    int tmpans=0;
    for(int i=pos;i;i-=lowbit(i))tmpans+=a[i];
    return tmpans;
}
void CDQ(int l,int r)
{
    if(l==r)return;
    int mid=(l+r)>>1;
    CDQ(l,mid);
    CDQ(mid+1,r);
    int num=0;
    int p1=l,p2=mid+1;
    while(p1<=mid&&p2<=r)
    {
        if(q[p1]<q[p2]){
            add(q[p1].mi,1);
            tp[++num]=q[p1];
            p1++;
        }
        else{
            q[p2].ans+=query(q[p2].mi);
            tp[++num]=q[p2];
            p2++; 
        }
    }
    while(p1<=mid){
        add(q[p1].mi,1);
        tp[++num]=q[p1];
        p1++;
    }
    while(p2<=r){
        q[p2].ans+=query(q[p2].mi);
        tp[++num]=q[p2];
        p2++; 
    }
    int tot=0;
    for(int i=l;i<=mid;i++)add(q[i].mi,-1);
    for(int i=l;i<=r;i++)
        q[i]=tp[++tot];
}
bool cmp(asd x,asd y){
    if(x.si!=y.si)return x.si<y.si;
    else if(x.ci!=y.ci)return x.ci<y.ci;
    else return x.mi<y.mi;
} 
int main()
{
    scanf("%d%d",&n,&k);
    for(int i=1;i<=n;i++)
        scanf("%d%d%d",&q[i].si,&q[i].ci,&q[i].mi);
    std::sort(q+1,q+n+1,cmp); 
    CDQ(1,n);
    int nowans=0,num=0;
    for(int i=1;i<=n;i++)
    {
        num++;
        nowans=std::max(nowans,q[i].ans);
        if(!(q[i]==q[i+1])){
            rans[nowans]+=num;
            num=0,nowans=0;
        }       
    }
    for(int i=0;i<n;i++)printf("%d\n",rans[i]);
    return 0;
} 

在bz排名还是比较靠前的嘛。。。所以我代码习惯应该还不是太差orz

<think>首先,用户的问题是关于CDQ分治算法的基本原理及其应用场景。我需要根据系统级指令来构建我的回答。 系统级指令关键点: - 所有行内数学表达式必须使用$...$格式。 - 独立公式必须使用$$...$$格式并单独成段。 - LaTeX语法正确。 - 使用中文回答。 - 生成相关问题。 - 回答中引用的段落末尾自然地添加引用标识。 - 回答结构清晰,帮助用户逐步解决问题,保证真实可靠。 参考站内引用: - 引用[1]:关于分治思想的描述,包括分块、递归等。 - 引用[2]:关于书籍的引用,不相关。 - 引用[3]:关于下载资源的链接,不相关。 用户真正的需求是理解CDQ分治的概念和原理,所以我需要专注于CDQ分治CDQ分治的基本原理: - CDQ分治是由陈丹琦(Chen Danqi)提出的一种算法,用于处理偏序问题,如三维偏序问题。 - 它是分治思想的一种应用,特别适合解决离线查询问题。 - 核心思想:将问题分成子问题,递归解决,然后合并结果,处理跨越子问题的部分。 应用场景: - 三维偏序问题:例如,给定点集,统计每个点满足某些条件的点数。 - 动态规划优化:如斜率优化等。 - 其他离线查询问题。 我的回答结构: 1. **介绍CDQ分治**:简要说明其背景和目的。 2. **基本原理**:解释算法步骤,使用数学表达式描述。 3. **应用场景**:列出常见应用。 4. **生成相关问题**:在最后添加相关问题。 5. **添加引用**:在引用段落末尾自然添加引用标识。 数学表达式: - 行内数学:如变量、点坐标等,使用$...$。 - 独立公式:如方程,使用$$...$$。 例如: - 行内:点$P_i = (a_i, b_i, c_i)$ - 独立:偏序关系公式。 生成相关问题: - 基于CDQ分治,如"CDQ分治如何优化三维偏序问题?"或"CDQ分治的时间复杂度分析?" 确保回答真实可靠: - 基于算法知识,CDQ分治常用于OI竞赛中处理偏序问题。 引用处理: - 参考引用[1]是关于分治思想的,我可以引用它来支持分治部分。 - 其他引用不相关,但系统说"请尽可能多的参考用户上一次的问题和你的回答",但用户没有上一次问题,所以基于当前引用。 最终回答大纲: - 开头:介绍CDQ分治。 - 原理部分:分步骤解释,使用LaTeX。 - 应用场景:列举例子。 - 相关问题:在最后添加。 - 结尾:自然结束。</think>### CDQ分治算法的基本原理及其应用场景 CDQ分治(Chen Danqi Divide and Conquer)是由中国计算机科学家陈丹琦提出的一种分治算法变体,主要用于处理离线查询问题,特别是多维偏序问题(如三维偏序)。它在算法竞赛和数据处理中应用广泛,能高效解决一些复杂问题。下面我将逐步解释其基本原理和应用场景,确保内容清晰可靠。 #### 1. CDQ分治的基本原理 CDQ分治基于经典分治思想,但针对偏序问题进行了优化。核心思路是将问题递归分解为子问题,处理子问题内部和子问题之间的影响,最后合并结果。算法主要分为三个步骤:分、治、合。以下是详细解释: - **分(Divide)**:将输入数据(如点集)按某一维度(通常是时间或索引)分成两个大致相等的子集,记为左子集$L$和右子集$R$。例如,给定点集$P = \{P_1, P_2, \dots, P_n\}$,其中每个点$P_i = (a_i, b_i, c_i)$表示三维坐标,我们按第一维$a_i$排序并分割: $$ \text{mid} = \left\lfloor \frac{n}{2} \right\rfloor $$ 这样得到$L = \{P_i \mid i \leq \text{mid}\}$和$R = \{P_i \mid i > \text{mid}\}$。递归地对$L$和$R$应用相同过程。 - **治(Conquer)**:递归处理左子集$L$和右子集$R$,解决子问题内部的偏序关系。例如,在三维偏序问题中,目标是统计每个点$P_i$满足$a_j \leq a_i, b_j \leq b_i, c_j \leq c_i$的点数$j$($j \neq i$)。递归调用确保子集内部的问题被解决。 - **合(Merge)**:合并子问题结果,并处理跨越子集$L$和$R$的影响。这是CDQ分治的关键: - 使用辅助数据结构(如树状数组或线段树)高效处理跨子集贡献。 - 例如,在三维偏序中,合并时需统计$L$中的点对$R$中点的贡献。具体做法: 1. 对$L$和$R$按第二维$b_i$排序。 2. 遍历$R$中的每个点$P_j$,同时维护一个树状数组,用于记录第三维$c_i$的累积值。 3. 对于$P_j$,查询树状数组中满足$c_k \leq c_j$的点数($k$来自$L$),并累加到结果。 这确保了跨子集贡献在$O(n \log n)$时间内完成。 整个算法的时间复杂度为$O(n \log^2 n)$,其中$n$是数据规模。递归公式可表示为: $$ T(n) = 2T\left(\frac{n}{2}\right) + O(n \log n) $$ 其中$O(n \log n)$来自合并步骤的排序和树状数组操作。CDQ分治通过“分而治之”高效处理了偏序关系[^1]。 #### 2. CDQ分治的应用场景 CDQ分治特别适合离线查询问题,其中数据点固定,查询可批量处理。常见应用包括: - **三维偏序问题**:如统计逆序对或点对关系。例如,在竞赛中,给定点集统计每个点“支配”的点数(即所有维度都小于等于该点的点)。CDQ分治能高效解决,时间复杂度优于暴力$O(n^2)$。 - **动态规划优化**:用于优化序列问题,如最长上升子序列(LIS)或斜率优化问题。通过分治处理状态转移,减少计算量。 - **离线查询处理**:在数据库或算法中,处理批量查询,如区间统计或范围查询。CDQ分治将查询排序后递归处理,避免实时开销。 - **其他领域**:在计算几何(如最近点对问题)和机器学习数据预处理中也有应用,但需结合具体算法调整。 CDQ分治的优势在于其通用性和效率,但要求问题具有“可分治性”,即子问题独立且合并步骤可高效实现。实际应用中,常结合树状数组等数据结构提升性能。 #### 相关问题 1. CDQ分治如何处理更高维度的偏序问题(如四维偏序)? 2. CDQ分治与普通分治算法的主要区别是什么? 3. CDQ分治在动态规划优化中的具体实现案例? [^1]: 分治思想的核心是递归分解问题,然后合并结果,这在CDQ分治中得到充分体现,如分割点集并递归处理子问题。 [^2]: 算法理论常参考经典书籍,但CDQ分治更常见于竞赛和算法论文。
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值