求一个点使可以任意跳跃而不会跳到陷阱里的问题

原题链接
在无穷大的地图里,一只兔子以d跳跃距离可以上下左右到处跳,有n(<=1e5)个矩形的陷阱,求是否存在一个点(x,y)使得兔子从(x+0.5,y+0.5)开始跳跃,可以任意跳跃而不会跳到陷阱里。
分析:
取以(0,0)为左下角,(d,d)为右上角的矩形,再将其他陷阱矩形以空隔为d"平移"到该矩形内,问题就转化成能否在完成所有平移后的矩形里找到一个未被陷阱覆盖的大小为1*1的正方形,答案即是该正方形的左下角。
我们可以以类似扫描线一样的写法,以0到d-1的长度建立线段树,当前高度小于d,则说明存在该矩阵,再从子树里找到高度小于d的最终小区间,从而得到答案。
注意,在注意陷阱矩阵时,由于坐标可能是负的,所以不妨让所有坐标全部加上(int)1e9,最终将答案坐标减去(int)1e9即可。
注意,不能将坐标离散化,不然最终在子树中求最终小区间时,会得到离散化的坐标而不是真正的答案。

struct SegmentTree{
    int l,r;
    LL len,cnt;
    SegmentTree(){
        len=0,cnt=0;
    }
}t[maxv*8];
struct A{
    LL x,y1,y2;
    int add;
};
vector<A> a;
bool cmp(A &a,A &b){
    return a.x<b.x;
}
void pushUp(int p){t[p].len=(t[p].cnt>0)?t[p].r+1-t[p].l:t[p*2].len+t[p*2+1].len;}  
void build(int p,int l,int r){
    t[p].l=l,t[p].r=r;
    if(l==r){
        return;
    }
    int mid=l+r>>1;
    build(p*2,l,mid);
    build(p*2+1,mid+1,r);
}
void change(int p,int l,int r,int d){
    if(l>r) return;
    if(t[p].l>=l&&t[p].r<=r){
        t[p].cnt+=d;
        pushUp(p);
        return;
    }
    int mid=t[p].l+t[p].r>>1;
    if(l<=mid) change(p*2,l,r,d);
    if(r>mid) change(p*2+1,l,r,d);
    pushUp(p);
}
int main(){
    int n,d,x1,x2,y1,y2,ansX=-1,ansY;
    scanf("%d %d",&n,&d);
    a.push_back({0,0,0,0});
    for(int i=1;i<=n;i++){
        scanf("%d %d %d %d",&x1,&y1,&x2,&y2);
        #define xl x1
        #define yl y1
        #define xr x2
        #define yr y2
        xl += 1e9, yl += 1e9, xr += 1e9, yr += 1e9;
        vector<pair<int,int> > xx,yy;
        if (xl / d == xr / d) {  //转移陷阱矩形
            xl %= d, xr %= d;
            xx.push_back({xl, xr});
        } else if (xr - xl >= d) {
            xx.push_back({0, d});
        } else {
            xl %= d, xr %= d;
            xx.push_back({xl, d});
            xx.push_back({0, xr});
        }
        if (yl / d == yr / d) {
            yl %= d, yr %= d;
            yy.push_back({yl, yr});
        } else if (yr - yl >= d) {
            yy.push_back({0, d});
        } else {
            yl %= d, yr %= d;
            yy.push_back({yl, d});
            yy.push_back({0, yr});
        }
        for(int i=0;i<xx.size();i++)
            for(int j=0;j<yy.size();j++){
                a.push_back({xx[i].first,yy[j].first,yy[j].second,1}),a.push_back({xx[i].second,yy[j].first,yy[j].second,-1});
            }
    }
    sort(a.begin()+1,a.end(),cmp);
    build(1,0,d-1);
    change(1,a[1].y1,a[1].y2-1,a[1].add);
    LL ans=0;
    for(int i=2;i<a.size();i++){
        if(a[i].x-a[i-1].x>0&&t[1].len>0&&t[1].len!=d){
            ansX=a[i-1].x;
            int cur=1;
            while(1){   //在子树里求最终子区间
                if(t[cur].l==t[cur].r){ansY=t[cur].l;break;}
                cur*=2;
                if(t[cur].len==t[cur].r+1-t[cur].l) cur++;
            }
            break;
        }
        change(1,a[i].y1,a[i].y2-1,a[i].add);
    }
    if(ansX!=-1) printf("YES\n%d %d",ansX-int(1e9),ansY-int(1e9));
    else printf("NO");
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值