zoukankan      html  css  js  c++  java
  • CH 4701

    题目链接:传送门

    关于CDQ分治(参考李煜东《算法竞赛进阶指南》):

    对于一系列操作,其中的任何一个询问操作,其结果必然等价于:初始值 + 此前所有的修改操作产生的影响。

    假设共有 $m$ 次操作,对于任意的满足 $1 le l le r le m$ 的正整数 $l,r$,定义 $solve(l,r)$ 为:对于任意的正整数 $k in [l,r]$,若第 $k$ 次操作为询问操作,则计算第 $l sim k-1$ 次操作中的修改操作对第 $k$ 次查询的影响。$solve(l,r)$ 可以通过分治来计算:

    1. 设 $mid = (l+r)>>1$,递归计算 $solve(l,mid)$ 和 $solve(mid+1,r)$。
    2. 计算第 $l sim mid$ 次操作中所有修改操作对第 $mid+1 sim r$ 次操作中所有询问操作的影响。

    同时,根据 $solve(l,r)$ 的定义,显然 $solve(1,m)$ 即为原始问题(不考虑初始值的情况下),而当 $l=r$ 时 $solve(l,r)$ 显然不需要进行任何计算。

    第 2 部分的操作,其实是一个静态问题,因为修改和询问是完全被前后分开的。类比于经典分治算法归并排序,我们只要让计算第 2 部分的时间复杂度只和 $r-l$ 线性相关,就可以使得总的时间复杂度为 $O(m log m)$。

    这种离线分治算法是基于时间顺序对操作序列进行分治的,因此称为基于时间的分治算法,或者更加广泛的名称CDQ分治

    题解:

    这个题,剥离题目背景,无非就是在二维平面上,在若干时刻放置一个点在某个位置,同时在若干时刻查询某个位置与其最近邻的距离。

    自然而然就会想起KDTree,不过我还不会带修改的KDTree怎么办,那不妨考虑一下一个简单的问题,假设没有修改操作,只在一开始就给定你平面上若干个点,后续全部都是查询操作。

    即:给定平面上 $n$ 个点 $(x_i,y_i)$,有 $m$ 次查询操作,每次询问距离 $(x,y)$ 位置的最近的点有多远(KDTree板子题)。显然,答案就是 $min_{1 le i le n}{ |x - x_i| + |y - y_i| }$。

    方便起见,为了去掉绝对值符号,不妨把询问变成询问四个方向(第 $1,2,3,4$ 象限方向)上距离最近的点。那么,以第三象限方向上的询问为例,其答案就变成:

    $min_{1 le i le n}{ x - x_i + y - y_i } = (x+y) - max_{1 le i le n}{x_i + y_i}$

    其中,$forall x_i,y_i$ 满足 $x_i le x$ 且 $y_i le y$。

    此时,可以考虑树状数组优化时间复杂度,先把所有平面上的点和询问中的位置按横坐标从小到大排个序,然后依次进行扫描。

    1. 若扫描到一个点 $(x_i,y_i)$,则在树状数组中把第 $y_i$ 个位置上的值和 $x_i+y_i$ 取较大值。
    2. 若扫描到一个询问的位置 $(x,y)$,则在树状数组中查询区间 $[0,y]$ 上的最大值。

    显然,按照横坐标从小到大扫描,使得每次询问时,树状数组中存储的值都是 $x$ 值不大于当前位置的点,而在区间 $[0,y]$ 的查询就迫使所有被考虑的点的 $y$ 值都是不超过当前位置的。

    时间复杂度 $O((n+m)log(n+m))$。

    然后,我们回到原来的问题,对于原问题,我们使用CDQ分治,那么需要考虑的问题就变成了 $solve(l,r)$ 中,如何计算第 $l sim mid$ 次操作中所有修改操作对第 $mid+1 sim r$ 次操作中所有询问操作的影响。换句话说,如何计算第 $l sim mid$ 次操作中所有添加进来的点,对第 $mid+1 sim r$ 次操作中所有询问的影响。这样一看,这不就是上面讲到的简化版问题吗。

    当然,为了保证每次计算静态问题的时间复杂度仅和 $r-l$ 相关,不能每次都建立一个新的树状数组,必须在每次计算 $solve(l,r)$ 后,把对树状数组的修改都撤销掉。也就是说,要保证对于每个 $solve(l,r)$,在进入和离开函数时,树状数组都是空的。

    这样一来,不妨把初始的 $n$ 个点也看做修改操作,对于总的 $n+m$ 次操作,总时间复杂度就是 $O((n+m) log^2 (n+m))$。

    AC代码:

    #include<bits/stdc++.h>
    using namespace std;
    const int MAX=1e6+10;
    const int INF=0x3f3f3f3f;
    
    int n,m;
    struct Rec{
        int t,x,y;
        Rec(){}
        Rec(int _t,int _x,int _y) {
            t=_t, x=_x, y=_y;
        }
        bool operator<(const Rec &b)const
        {
            if(x==b.x) return y<b.y;
            else return x<b.x;
        }
    }o[MAX];
    int tot;
    Rec s[MAX];
    int ans[MAX];
    
    int C[MAX],N;
    inline int lowbit(int x){return x&-x;}
    int ask(int pos) {
        int res=-INF;
        for(;pos>=1;pos-=lowbit(pos)) res=max(res,C[pos]);
        return res;
    }
    void upd(int pos,int val) {
        for(;pos<=N;pos+=lowbit(pos)) C[pos]=max(C[pos],val);
    }
    
    //第一象限:- x - y - max(- xi - yi)
    //第二象限:+ x - y - max(+ xi - yi)
    //第三象限:+ x + y - max(+ xi + yi)
    //第四象限:- x + y - max(- xi + yi)
    void calc(int st,int ed,int d,int sx,int sy)
    {
        for(int i=st;i!=ed;i+=d)
        {
            int pos=(sy==1)?s[i].y:N-s[i].y;
            int val=sx*s[i].x+sy*s[i].y;
            if(o[s[i].t].t==1) upd(pos,val);
            else ans[s[i].t]=min(ans[s[i].t],val-ask(pos));
        }
        for(int i=st;i!=ed;i+=d)
        {
            int pos=(sy==1)?s[i].y:N-s[i].y;
            if(o[s[i].t].t==1) for(;pos<=N;pos+=lowbit(pos)) C[pos]=-INF;
        }
    }
    
    void cdqdiv(int l,int r)
    {
        if(l>=r) return;
        int mid=(l+r)>>1;
        cdqdiv(l,mid);
        cdqdiv(mid+1,r);
        tot=0;
        for(int i=l;i<=mid;i++) if(o[i].t==1) s[++tot]=Rec(i,o[i].x,o[i].y);
        for(int i=mid+1;i<=r;i++) if(o[i].t==2) s[++tot]=Rec(i,o[i].x,o[i].y);
        sort(s+1,s+tot+1);
        calc(tot,0,-1,-1,-1); //第一象限
        calc(1,tot+1,1,1,-1); //第二象限
        calc(1,tot+1,1,1,1); //第三象限
        calc(tot,0,-1,-1,1); //第四象限
    }
    
    int main()
    {
        scanf("%d%d",&n,&m); m+=n;
        for(int i=1;i<=n;i++) o[i].t=1, scanf("%d%d",&o[i].x,&o[i].y), ++o[i].y;
        for(int i=n+1;i<=m;i++) scanf("%d%d%d", &o[i].t,&o[i].x,&o[i].y), ++o[i].y;
    
        for(int i=1;i<=m;i++) N=max(N,o[i].y); N++;
        for(int i=1;i<=N;i++) C[i]=-INF;
    
        memset(ans,0x3f,sizeof(ans));
        cdqdiv(1,m);
        for(int i=1;i<=m;i++) if(o[i].t==2) printf("%d
    ",ans[i]);
    }
  • 相关阅读:
    自定义滚动条原理
    多个轮播图或者选项卡显示在一个页面是,使用代码重用
    浮动与清除
    cni 添加网络 流程分析
    《MapReduce: Simplified Data Processing on Large Cluster 》翻译
    OpenStack overview 笔记
    docker containerd shim分析
    docker containerd 中的create 容器操作
    MIT jos 6.828 Fall 2014 训练记录(lab 6)
    docker containerd中的容器操作
  • 原文地址:https://www.cnblogs.com/dilthey/p/10034784.html
Copyright © 2011-2022 走看看