BZOJ 2716 天使玩偶 CDQ分治

删除回忆录丶 提交于 2020-01-19 22:30:55

题意:

输入:

输出:

样例较长这里省略

思路:

算法:CDQ分治+树状数组

首先,题目中存在绝对值,肯定是去掉比较容易,所以第一种情况是考虑分类讨论,即将查询的点当做原点,然后分四个象限分别拆绝对值,但这个时候会发现其实每个象限的算法区别不大,因此另一种想法就是每次都将所有的转到第三象限中,即通过加减转换,此时对于需要查询的\(A\)点,玩偶的位置\(B\)到达的距离都可以直接变为\(dis=A_x+A_y-(B_x+B_y)\)由于\(A_x+A_y\)的值是固定的,需要dis最小,所以我们要找到\(B_y+B_x\)最大的点。

由于我们直接设定的是在第三个象限,所以找到\(B\)点存在限制\(B_y<=A_y\)\(B_x<A_x\) 再加上一个时间限制,就是一个偏序问题。下面的思路是:先按照时间排序,在对\(x\)进行分治,用树状数组维护y的值。

因为这题数据很卡,下面是几个优化
1、对于时间排序,每一次sort的复杂度是\(O(nlogn)\) 一共要进行四次,那么复杂度就直接炸了,但是实际上我们可以记下最开始的排列, 每一次更改变换直接用最开始的排列。

for(int i=1; i<=tot; i++)ans[i]=INF,P[i]=a[i];//这里的P数组就是用来记录初始序列
CDQ(1,tot);
for(int i=1; i<=tot; i++)P[i].x=Mx-P[i].x,a[i]=P[i];

2、在CDQ中\(x\)的排序用可以考虑归并

3、对于开始的\([1,n]\)个数只有加入,所以可以直接排序返回

if(r<=n){
    sort(a+l,a+r+1);
    return;
}

同时题目中还有个细节:\(x,y\)可能等于0,所以可以考虑都加一,防止树状数组陷入死循环。

附代码+注释:

#include<bits/stdc++.h>
#define M 500005
#define INF 1000000000
#define N 1000005
#define lowbit(x) (x&-x)
using namespace std;
int n,m,ans[M<<1],Mx;
void Rd(int &res) {
    res=0;
    char c;
    while(c=getchar(),c<48);
    do res=(res<<1)+(res<<3)+(c-'0');
    while(c=getchar(),c>=48);
}
struct node {
    int op,x,y,id;
    bool operator<(const node&_)const {
        if(x!=_.x)return x<_.x;
        return y<=_.y;
    }
} a[M<<1],p[M<<1],P[M<<1];
struct Tree {
    int mx[N];
    void Init() {
        memset(mx,0,sizeof(mx));
    }
    void add(int x,int y) {
        while(x<=Mx)mx[x]=max(mx[x],y),x+=lowbit(x);
    }
    int find(int x) {
        int res=0;
        while(x)res=max(res,mx[x]),x-=lowbit(x);
        return res;
    }
    void clear(int x) {
        while(x<=Mx)mx[x]=0,x+=lowbit(x);
    }
} T;
void CDQ(int l,int r) {
    if(r<=n){
        sort(a+l,a+r+1);
        return;
    }
    if(l>=r)return;
    int mid=(l+r)>>1;
    int L=l,R=mid+1;
    int now=l;
    CDQ(l,mid),CDQ(mid+1,r);//正常的归并排序过程中加入计算
    while(L<=mid&&R<=r) {
        if(a[L]<a[R]) {
            if(a[L].op==1)T.add(a[L].y,a[L].x+a[L].y);
            p[now++]=a[L++];
        } else {
            if(a[R].op==2) {
                int t=T.find(a[R].y);
                if(t)ans[a[R].id]=min(ans[a[R].id],a[R].x+a[R].y-t);//t>0表示存在这样的点 因为开始已经将x,y都加了1
            }
            p[now++]=a[R++];
        }
    }
    while(R<=r) {
        if(a[R].op==2) {
            int t=T.find(a[R].y);
            if(t)ans[a[R].id]=min(ans[a[R].id],a[R].x+a[R].y-t);
        }
        p[now++]=a[R++];
    }
    for(int i=l; i<L; i++)if(a[i].op==1)T.clear(a[i].y);//撤销
    while(L<=mid)p[now++]=a[L++];
    for(int i=l; i<=r; i++)a[i]=p[i];
}
int main() {
//  freopen("data.in","r",stdin);
//  freopen("data.out","w",stdout);
    Rd(n),Rd(m);
    for(int i=1; i<=n; i++)Rd(a[i].x),Rd(a[i].y),a[i].op=1,a[i].x++,a[i].y++,Mx=max(Mx,max(a[i].x,a[i].y)),a[i].id=i;
    for(int i=n+1; i<=m+n; i++)Rd(a[i].op),Rd(a[i].x),Rd(a[i].y),a[i].x++,a[i].y++,Mx=max(Mx,max(a[i].x,a[i].y)),a[i].id=i;
    int tot=n+m;Mx++;
    T.Init();
    for(int i=1; i<=tot; i++)ans[i]=INF,P[i]=a[i];//下面是对四个方向的计算
    CDQ(1,tot);
    for(int i=1; i<=tot; i++)P[i].x=Mx-P[i].x,a[i]=P[i];
    CDQ(1,tot);
    for(int i=1; i<=tot; i++)P[i].y=Mx-P[i].y,a[i]=P[i];
    CDQ(1,tot);
    for(int i=1; i<=tot; i++)P[i].x=Mx-P[i].x,a[i]=P[i];
    CDQ(1,tot);
    for(int i=1; i<=tot; i++)if(ans[i]!=INF)printf("%d\n",ans[i]);
    return 0;
}
标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!