《算法竞赛进阶指南》0x47离线分治算法 CDQ分治

题目链接:https://www.acwing.com/problem/content/256/

题目给出n个初始点,m个操作,可能是加上一个新的点,也可能是给出一个点,查询离他曼哈顿距离最近的点的距离。由于是动态问题,有查询与改动是无序的,所以考虑使用基于时间的离线分治算法CDQ分治。该算法在递归计算左右的情况之后只需要计算左半边的更新对右半边的查询的影响。故变成了一个静态的问题,这时候将曼哈顿距离展开成四个查询即可,由于状态控制有两个,通过排序解决一个状态控制,另一个可以通过树状数组查询前缀最大值解决。树状数组每次进行calc之后都要回到原状态,不能每次都开一个树状数组。(实际上AcWing就过了17/20的数据,人没了)

代码:

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
const int maxn = 1000010;
const int inf=(1<<30);
struct rec{int x,y,z;};
rec a[maxn];//操作序列
rec b[maxn];//静态问题的坐标,及其下标
int c[maxn],tot;//树状数组
int ans[maxn],n,m,t;
inline bool operator < (const rec& a,const rec& b){
    return a.x<b.x || (a.x==b.x && a.y<b.y);
}
inline int read(){
    int ans=0,w=1;
    char ch=getchar();
    while(!isdigit(ch)){if(ch=='-')w=-1;ch=getchar();}
    while(isdigit(ch))ans=(ans<<3)+(ans<<1)+ch-'0',ch=getchar();
    return ans*w;
}
//查询前缀最大值 
inline int ask(int x){
    int ans=-inf;
    while(x){
        ans=max(ans,c[x]);
        x-=x&(-x);
    }
    return ans;
} 
//维护前缀最大值 
inline void update(int x,int y){
    while(x<tot){
        c[x]=max(c[x],y);
        x+=x&(-x); 
    }
}
inline void calc(int st,int ed,int de,int dx,int dy){
    for(int i=st;i!=ed;i+=de){
        int y=(dy==1)?b[i].y:tot-b[i].y;//查询的是前缀 
        int tmp=dx*b[i].x+dy*b[i].y;
        if(a[b[i].z].z==1)update(y,tmp);
        else ans[b[i].z]=min(ans[b[i].z],tmp-ask(y));
    }
    for(int i=st;i!=ed;i+=de){
        int y=(dy==1)?b[i].y:tot-b[i].y;
        if(a[b[i].z].z==1){//如果是一个更新就将树状数组更新回原状态 
            for(int j=y;j<tot;j+=j&(-j))c[j]=-inf; 
        }    
    }
}
inline void cdqdiv(int l,int r){
    int mid=(l+r)>>1;
    if(l<mid)cdqdiv(l,mid);
    if(mid+1<r)cdqdiv(mid+1,r);
    t=0;//前半段的修改数量+后半段的询问数量
    for(int i=l;i<=r;i++){
        if(i<=mid && a[i].z==1 || i>mid && a[i].z==2)
            b[++t]=a[i],b[t].z=i;//保存下标,用来更新ans 
    } 
    sort(b+1,b+t+1); 
    calc(1,t+1,1,1,1);//计算[l,mid]段的修改对[mid+1,r]段的查询的贡献 
    calc(1,t+1,1,1,-1);
    calc(t,0,-1,-1,-1);
    calc(t,0,-1,-1,1);
}
int main(){
    cin>>n>>m;
    m+=n;
    //把初始的点也认为是一种加点的操作 
    //由于树状数组中下标是从1开始的,所以y++ 
    for(int i=1;i<=n;i++)
        a[i].x=read(),a[i].y=read(),a[i].y++,a[i].z=1;
    for(int i=n+1;i<=m;i++)
        a[i].z=read(),a[i].x=read(),a[i].y=read(),a[i].y++;
    
    for(int i=1;i<=m;i++)tot=max(tot,a[i].y);//树状数组下标的最大值
    tot++;
    memset(ans,0x3f,sizeof(ans));
    memset(c,0xcf,sizeof(c));//赋值,一个绝对值极大的负数
    
    cdqdiv(1,m);
    for(int i=1;i<=m;i++)
        if(a[i].z==2)printf("%d
",ans[i]); 
} 
每一个不曾起舞的日子,都是对生命的辜负。
原文地址:https://www.cnblogs.com/randy-lo/p/13359581.html