【算法】CDQ分治初探

  CDQ分治是处理数据结构题的有力武器,通俗的讲,它可以替代一层数据结构,从而达到降低代码难度以及常数的作用,缺点是必须离线。

  CDQ分治一般可以用来处理偏序问题以及斜率优化DP问题。

  与普通分治不同的是,CDQ分治左区间的答案对右区间有贡献,最经典的例子是归并排序求逆序对。

  下面先讲讲偏序问题:

    二维偏序本质上和归并排序求逆序对一样,不多提及。

    三维偏序:第一维直接排序,第二维用CDQ分治,第三维用树状数组。

    具体地讲讲CDQ分治求偏序。因为我们第一维已经排序了,那么绝对不会出现左区间的第一维比右区间的第一维大的情况。

    那么递归完左右区间之后,将左右区间都按第二维排序,这时我们就可以统计左区间对右区间的元素的贡献了,因为右区间的第二维是有序的,于是可以扫一遍右区间,每次找到左区间最大的第二维小于当前枚举到的右区间元素的第二维的元素,用树状数组维护第三维,更新答案。

    四维偏序:实际上可以使用CDQ分治套CDQ分治的方式,只需要将上面的BIT改成CDQ分治就好了,照这么讲,n维偏序都可做了不是吗?

    $\geq$五维偏序:...是可做,但是复杂度上天。五维偏序往上就不如$O(n^2)$了。。。

求偏序问题的CDQ分治基本流程:

  若$l==r$ 则返回。

  分治左区间,分治右区间。

  统计左区间对右区间的贡献。

  消除BIT上的贡献(套CDQ分治就不需要)。

三维偏序代码:

#include<iostream>
#include<cstring>
#include<cstdlib>
#include<cstdio>
#include<algorithm>
using namespace std;
const int maxn=500010;
struct poi{int a, b, c, cnt, ans;}v[maxn], tmp[maxn];
int n, k, tott;
int ans[maxn], cnt[maxn], tree[maxn], q[maxn];
inline void read(int &k)
{
    int f=1; k=0; char c=getchar();
    while(c<‘0‘ || c>‘9‘) c==‘-‘ && (f=-1), c=getchar();
    while(c<=‘9‘ && c>=‘0‘) k=k*10+c-‘0‘, c=getchar();
    k*=f;
}
inline bool cmp2(poi a, poi b){return a.b<b.b||(a.b==b.b && a.c<b.c);}
inline bool cmp(poi a, poi b){return a.a<b.a||(a.a==b.a && cmp2(a, b));}
inline void update(int x, int delta){for(;x<=k;x+=x&-x) tree[x]+=delta;};
inline int query(int x){int sum=0; for(;x;x-=x&-x) sum+=tree[x]; return sum;}
void solve(int l, int r)
{
    if(l==r) return;
    int mid=(l+r)>>1;
    solve(l, mid); solve(mid+1, r);
    sort(v+l, v+mid+1, cmp2);
    sort(v+mid+1, v+r+1, cmp2);
    int L1=l, L2=mid+1;
    while(L2<=r)
    {
        while(L1<=mid && v[L1].b<=v[L2].b) update(v[L1].c, v[L1].cnt), ++L1;
        v[L2].ans+=query(v[L2].c); ++L2;
    }
    for(int i=l;i<L1;i++) update(v[i].c, -v[i].cnt);
}
int main()
{
    read(n); read(k);
    for(int i=1;i<=n;i++) read(tmp[i].a), read(tmp[i].b), read(tmp[i].c);
    sort(tmp+1, tmp+1+n, cmp);
    for(int i=1, j=1;i<=n;i=j)
    {
        v[++tott]=tmp[i];
        while(tmp[i].a==tmp[j].a && tmp[i].b==tmp[j].b && tmp[i].c==tmp[j].c && j<=n)
        j++, v[tott].cnt++;
    }
    solve(1, tott);
    for(int i=1;i<=tott;i++) cnt[v[i].ans+v[i].cnt-1]+=v[i].cnt;
    for(int i=0;i<n;i++) printf("%d\n", cnt[i]);
}

  斜率优化DP用CDQ也可以去掉一层数据结构,但是写法和偏序略微有些不同,按上面的做法可能会多一个$log$用于排序。

  我们用左区间去更新右区间的时候,显然左右区间需要排序的东西是不一样的,这时候我们就不能简单的按时间排序后分治了。

  这时有个非常喵喵的做法。先按等式右边排序,每次分治的时候,我们可以用$O(n)$的时间把编号$\leq mid$的分到左边,$> mid$的分到右边,但是如果一开始按编号排序,是无法做到$O(n)$时间把斜率分到左右两边的。

  每次我们先递归左区间,每次退出区间的时候把这个区间按照横坐标归并排序,而我们一开始已经按照等式右边排序了,所以递归完左区间后当前区间就是左区间横坐标有序,右区间按等式右边有序的情况,这时候我们就可以$O(n)$用单调队列做到斜率优化DP了。

斜率优化DP用CDQ分治的基本流程:

  若l==r 则返回。

  把左区间按编号把$\leq mid$的分到左边,$>mid$的分到右边。

  分治左区间。

  此时左区间横坐标有序,右区间按等式右边有序,所以用单调队列斜率优化DP。

  分治右区间。

  按等式右边归并排序。

这个的代码可以参考下方例题bzoj1492: [NOI2007]货币兑换Cash。

施工中...

y[j]=f[j]/(a[j]*rate[j]+b[j])*rate[j]

x[j]=f[j]/(a[j]*rate[j]+b[j])

x[j]*a[i]+y[j]*b[i]<x[k]*a[i]+y[k]*b[i]

(x[j]-x[k])*a[i]<(y[k]-y[j])*b[i]

(x[j]-x[k])/(y[k]-y[j])<b[i]/a[i]

#include<iostream>
#include<cstring>
#include<cstdlib>
#include<cstdio>
#include<algorithm>
#include<cmath>
using namespace std;
const int maxn=500010, inf=1e9;
struct poiq{double a, b, rate, k; int pos;}q[maxn], nq[maxn];
struct poip{double x, y;}p[maxn], np[maxn];
int n, que[maxn], st[maxn];
double f[maxn];
bool operator < (poiq a, poiq b){return b.k-a.k>1e-9;}
inline double xl(int a, int b){return fabs(p[b].x-p[a].x)>1e-9?(p[a].y-p[b].y)/(p[b].x-p[a].x):-inf;}
void solve(int l, int r)
{
    if(l==r)
    {
        f[l]=max(f[l], f[l-1]);
        p[l].x=f[l]/(q[l].a*q[l].rate+q[l].b);
        p[l].y=p[l].x*q[l].rate;
        return;
    }
    int mid=(l+r)>>1, L1=l, L2=mid+1;
    for(int i=l;i<=r;i++)
    if(q[i].pos<=mid) nq[L1++]=q[i]; else nq[L2++]=q[i];
    for(int i=l;i<=r;i++) q[i]=nq[i]; solve(l, mid);
    int L=1, R=0;
    for(int i=l;i<=mid;i++)
    {
        while(L<R && xl(que[R-1], que[R])-xl(que[R], i)>1e-9) R--;
        que[++R]=i;
    }
    for(int i=mid+1;i<=r;i++)
    {
        while(L<R && q[i].k-xl(que[L], que[L+1])>1e-9) L++;
        f[q[i].pos]=max(f[q[i].pos], q[i].a*p[que[L]].y+q[i].b*p[que[L]].x);
    }
    solve(mid+1, r);
    L1=l, L2=mid+1, L=l;
    while(L1<=mid && L2<=r)
    if(p[L2].x-p[L1].x>1e-9) np[L]=p[L1], L1++, L++; else np[L]=p[L2], L2++, L++;
    while(L1<=mid) np[L]=p[L1], L1++, L++;
    while(L2<=r) np[L]=p[L2], L2++, L++;
    for(int i=l;i<=r;i++) p[i]=np[i];
}
int main()
{
    scanf("%d%lf", &n, &f[0]);
    for(int i=1;i<=n;i++)
    {
        scanf("%lf%lf%lf", &q[i].a, &q[i].b, &q[i].rate);
        q[i].k=q[i].b/q[i].a; q[i].pos=i;
    }
    sort(q+1, q+1+n); solve(1, n);
    printf("%.3lf\n", f[n]);
    return 0;
}

原文地址:https://www.cnblogs.com/Sakits/p/8316856.html

时间: 2024-11-10 01:01:43

【算法】CDQ分治初探的相关文章

算法复习——cdq分治

题目: Description 有n朵花,每朵花有三个属性:花形(s).颜色(c).气味(m),又三个整数表示.现要对每朵花评级,一朵花的级别是它拥有的美丽能超过的花的数量.定义一朵花A比另一朵花B要美丽,当且仅当Sa>=Sb,Ca>=Cb,Ma>=Mb.显然,两朵花可能有同样的属性.需要统计出评出每个等级的花的数量. Input 第一行为N,K (1 <= N <= 100,000, 1 <= K <= 200,000 ), 分别表示花的数量和最大属性值. 以下

CDQ 分治算法模板

CDQ分治 1.三维偏序问题:三维偏序(陌上花开) #include<bits/stdc++.h> #define RG register #define IL inline #define _ 200005 using namespace std; IL int gi(){ RG int data = 0 , m = 1; RG char ch = 0; while(ch != '-' && (ch<'0'||ch>'9'))ch = getchar(); if(

【算法】CDQ分治 -- 三维偏序 &amp; 动态逆序对

初次接触CDQ分治,感觉真的挺厉害的. 整体思路即分而治之,再用之前处理出来的答案统计之后的答案. 大概流程是: 对于区间 l ~ r : 1.处理 l ~mid, mid + 1 ~ r 的答案 2.分别排序规整 3.计算 l ~ mid 中每一个数对 mid + 1 ~ r 中的答案的贡献, 累加 4.得到区间l ~ r的答案 CDQ分治我一共也才做了两道题目, 就一起整理在这里了.大体都差不多,CDQ+树状数组分别维护两个维度. 1.三维偏序 #include <bits/stdc++.h

BZOJ1176---[Balkan2007]Mokia (CDQ分治 + 树状数组)

题目链接:http://www.lydsy.com/JudgeOnline/problem.php?id=1176 CDQ第一题,warush了好久.. CDQ分治推荐论文: 1 <从<Cash>谈一类分治算法的应用> 陈丹琦 2 <浅谈数据结构题的几个非经典解法>  许昊然 关于CDQ分治,两种要求:①操作不相互影响  ②可以离线处理 题目描述是有问题的,,初始时 全部为0,不是s 题意:二维平面内,两种操作,1 x y v ,位于(x,y)的值加上v...2 x1,

学习笔记: cdq分治

今年的课程有很大一部分内容是cdq分治及其扩展(也就是二进制分组),拜读后觉得还是蛮有用的,这里小小地总结一下.(话说自己草稿箱里还有好多学习笔记的半成品呢,真是弱爆了.顺便感谢下fy与wxl向我介绍了那么好的东西) 推荐论文: 1 <从<Cash>谈一类分治算法的应用> 陈丹琦 2 <浅谈数据结构题的几个非经典解法>  许昊然 Q: cdq分治和普通的分治有什么区别? A: 在我们平常使用的分治中,每一个子问题只解决它本身(可以说是封闭的).而在cdq分治中,对于划分

初学CDQ分治-NEU1702

关于CDQ分治,首先需要明白分治的复杂度. T(n) = 2T(n/2)+O(kn), T(n) = O(knlogn) T(n) = 2T(n/2)+O(knlogn), T(n) = O(knlog^2n) T(n) = 2T(n/2)+O(k), T(n) = O(kn) 那么我们要处理[l, r]内的询问,我们可以分别处理[l, m]和[m+1, r]的询问,然后以较小的复杂度计算出[l, m]对[m+1, r]的贡献. 最简单的cdq就是三维偏序问题. 两点(x1, y1, z1)和(

【BZOJ】1492: [NOI2007]货币兑换Cash(cdq分治)

http://www.lydsy.com/JudgeOnline/problem.php?id=1492 蒟蒻来学学cdq神算法啊.. 详见论文 陈丹琦<从<Cash>谈一类分治算法的应用> orz 此题表示被坑精度.....导致没1a...开小号交了几发....................坑. 蒟蒻就说说自己的理解吧.. 首先这题神dp...(表示完全看不出来) 首先我们要最大化钱,那么可以将问题转化为最大化A券!(或B券)!!!!这点太神了,一定要记住这些!! 设d[i]表

ACdream 1157 (cdq分治)

题目链接 Segments Time Limit: 4000/2000MS (Java/Others)Memory Limit: 20000/10000KB (Java/Others) Problem Description 由3钟类型操作:1)D L R(1 <= L <= R <= 1000000000) 增加一条线段[L,R]2)C i (1-base) 删除第i条增加的线段,保证每条插入线段最多插入一次,且这次删除操作一定合法3) Q L R(1 <= L <= R

bzoj 2726 [SDOI2012]任务安排(斜率DP+CDQ分治)

[题目链接] http://www.lydsy.com/JudgeOnline/problem.php?id=2726 [题意] 将n个任务划分成若干个块,每一组Mi任务花费代价(T+sigma{ tj }+s)*sima{ fi },j属于Mi,T为当前时间,问最小代价. [思路] 设f[i]为将前i个任务划分完成的最小费用,Ti Fi分别表示t和f的前缀和,则不难写出转移方程式: f[i]=min{ f[j]+(F[n]-F[j])*(T[i]-T[j]+s) },1<=j<=i-1 经过