zoukankan      html  css  js  c++  java
  • CDQ分治学习笔记

    CDQ分治是2008 IOI金牌神仙陈丹琦在国家集训队引入的一种离线分治算法,

    对时间分治,时间复杂度为O(logn*单次处理复杂度)。

    思路大体是这样的:

    数据结构问题的操作通常可以分为修改和查询两类,而每一次查询就是询问前面所有的修改对当前的影响,

    而CDQ分治将动态的问题分解为一个个静态的,对时间点计算影响的问题,并用分治的方法统一求解。

    当前有N个操作,我们用solve(l,r)计算在[l,r]区间内的修改对区间内查询的贡献,做法如下:

      设mid=(l+r)/2,

      1.分治计算solve(l,mid)

      2.分治计算solve(mid+1,r)

      3.计算[l,mid]内所有的修改对[mid+1,r]的查询的影响

    solve(1,N)是调用入口,当l==r时,只有一项操作,可以直接返回。

    我们将原来的动态问题分解成了一个个步骤3的静态问题,其数量是(1+2+4+...+2k-1+C)=O(N)个,其中C<=2^k<=N。

    而每一个原来的询问由O(logN)个静态问题组成,由于总共递归O(logN)层,所以复杂度是

      O(logN*单次处理复杂度)。

    因为每一个静态问题的时复只与当前的l,r有关,因此效率较高。

    例题   天使的玩偶https://www.luogu.com.cn/problem/P4169

      动态维护查询从任意一点到可能有玩偶的地方的最近曼哈顿距离。

    对于每个查询,要计算min{|x-xi|+|y-yi|},为了去掉绝对值符号,我们将询问分成四瓣,分别查询当前点左下、左上、右下、右上的最近距离。

    将其分解成

      x+y-max{xi+yi}

      x-y-max{xi-yi}

      -x+y-max{-xi+yi}

      -x-y-max{-xi-yi}

    在计算步骤3时,枚举四个方向,

    为了保证计算到的点都在正确的方向,可以先将x排序,而y查询前缀最大值或后缀最大值,

    并用树状数组维护最大值即可。

    单次静态计算O(nlogn),总复杂度O(nlog2n),为了保证复杂度,每次树状数组都要倒过来还原,不能直接清空。

    实现细节看代码

    #include<iostream>
    #include<cstdio>
    #include<algorithm>
    #include<cstring>
    #define MAX 300010
    #define LIM 1000001
    #define Inf 0x3f3f3f3f
    #define lowbit(x) x&-x
    #define PII pair<int,int>
    #define mk make_pair
    #define ft first
    #define sc second
    using namespace std;
    struct pos{
        int x,y;
        int type,ans;
    }p[MAX*2];
    inline int read(){
        int s=0,w=1;
        char ch=getchar();
        while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
        while(ch>='0'&&ch<='9')s=(s<<3)+(s<<1)+(ch^48),ch=getchar();
        return s*w;
    }
    int t[LIM],cnt,q[MAX*2];
    PII opt[MAX*2];
    //树状数组
    void update(int x,int k){ for(;x<=LIM&&t[x]<k;x+=lowbit(x))t[x]=k; } int get(int x){ int res=-Inf; for(;x;x-=lowbit(x))res=max(res,t[x]); return res; } void del(int x){ for(;x<=LIM;x+=lowbit(x))t[x]=-Inf; } int type(int x,int y,int id){ switch(id){ case 0:return x+y; case 1:return x-y; case 2:return -x+y; case 3:return -x-y; } } bool cmp(int a,int b){return p[a].x<p[b].x;} void work(int l,int r){ int mid=(l+r)/2,x,y; int num; cnt=0; for(int i=l;i<=mid;i++) if(!p[i].type)q[cnt++]=i; for(int i=mid+1;i<=r;i++) if(p[i].type)q[cnt++]=i;
       //统计需计算的操作,即[l,mid]中的修改和[mid+1,r]中的查询 sort(q,q
    +cnt,cmp); for(int id=0;id<4;id++){ num=-1; for(int i=(id<2?0:cnt-1);(id<2?i<cnt:i>=0);i+=(id<2?1:-1)){ //不同方向不同的处理顺序 x=p[q[i]].x,y=p[q[i]].y; if(!p[q[i]].type){ opt[++num]=mk(id&1?LIM-y:y,type(x,y,id)); //记录稍后要还原的修改,LIM-y就是将后缀最大值转成前缀的 update(opt[num].ft,opt[num].sc); } else{ if(num==-1)continue; //如果之前没有修改就不用更新查询,去掉不影响正确性,但可以优化常数 p[q[i]].ans=min(p[q[i]].ans,type(x,y,id)-get(id&1?LIM-y:y)); } } for(int i=0;i<=num;i++){ del(opt[i].ft);//撤销修改 } } }
    //cdq分治
    void cdq(int left,int right){ if(left==right)return; int mid=(left+right)/2; cdq(left,mid); cdq(mid+1,right); work(left,right); } int main(){ int n,m; cin>>n>>m; for(int i=0;i<n+m;i++){ if(i>=n)p[i].type=read()-1; else p[i].type=0; //我们视初始状态也为修改 p[i].x=read(),p[i].y=read(); p[i].ans=2*LIM; //答案最大可以达到2e6 } memset(t,0x80,sizeof(t));//0x80808080在int中为-2139062144 cdq(0,n+m-1); for(int i=0;i<n+m;i++){ if(p[i].type)printf("%d ",p[i].ans); } return 0; }
  • 相关阅读:
    Spring Cloud Gateway配置自定义异常返回
    C#开机启动,托盘图标等小功能
    微信内置浏览器搞事情之调试模式
    物联网架构成长之路(56)-SpringCloudGateway+JWT实现网关鉴权
    物联网架构成长之路(55)-Gateway+Sentinel实现限流、熔断
    物联网架构成长之路(53)-Sentinel流量控制中间件入门
    物联网架构成长之路(54)-基于Nacos+Gateway实现动态路由
    物联网架构成长之路(52)-基于Nacos+prometheus+grafana的监控
    物联网架构成长之路(51)-Nacos微服务配置中心、服务注册服务发现
    物联网架构成长之路(50)-EMQ配置SSL证书,实现MQTTs协议
  • 原文地址:https://www.cnblogs.com/Neal-lee/p/13544726.html
Copyright © 2011-2022 走看看