题目链接
题目大意
有一只兔子想在草原上随意地跳动,它每一跳距离为 d d d,而猎人在草原上布置了 n n n个矩形陷阱,给出每个陷阱的左下角坐标和右上角坐标,求兔子的初始位置 ( x 0 + 0.5 , y 0 + 0.5 ) ( − 1 e 9 ≤ x 0 , y 0 ≤ 1 e 9 ) (x_0+0.5,y_0+0.5)(-1e9 \leq x_0,y_0 \leq 1e9) (x0+0.5,y0+0.5)(−1e9≤x0,y0≤1e9)使得它永远不会碰到陷阱,输出 x 0 , y 0 x0,y0 x0,y0.
题解
由于兔子可以无限跳动,而陷阱在草原各地,所以我们将陷阱限制在兔子的一次跳动范围内(二维),而兔子如果在它范围内没有一个落脚点则输出NO,其它情况任意找一个点落脚即可。
下面我们考虑如何实现:
首先将陷阱限制,我们将其范围取模①,由于每个陷阱的范围都没有规定,所以范围可能重合,你想到了什么,没错,就是容斥(容斥复杂度太高),啊呸,扫描线。对于重复的部分,我们仅需要留下一块即可,如图:
所谓扫描线就像它的名字一样,不断扫过去,当有块进入或离开时加上一条线,这样就可以将整个图块分成若干块,而它的面积就是每一块长乘宽 废话 而扫描线就是线段树的升级版。对于这道题目,我们将其按上图处理。
然后扫一下该范围将
i
i
i行第一条总陷阱的长度求出②,如果在第
i
i
i行有落脚的地方,即其小于
d
d
d,则将其输出,并二分扫一次找到该落脚点输出即可。
注:
①
由于每次找到的点为
x
0
+
0.5
,
y
0
+
0.5
x_0+0.5,y_0+0.5
x0+0.5,y0+0.5所以每个陷阱可以踩后面,但不能踩前面,所以代码处将末尾
−
−
--
−−;
若处理中范围过大则直接给它定义成最大值;
若取模后前后颠倒,则分为四种情况考虑(参考代码处);
取模后可能为负数,故再加上进行二次取模。
②
这里我们考虑,
若第一块占满所有地方则该行无解,
若没有,则它与第二块必有一个以上的间隔,故可以找到应有的点。
参考代码
#include<bits/stdc++.h>
const int N=5e5+5;
int n,d,ql,qr;
struct node{
int l,r,x;
};
std::vector<node> v[N];
struct p{
int s,len;
}q[N];
void add(int x1,int x2,int y1,int y2)
{
v[x1].push_back(node{y1,y2,1});
v[x2+1].push_back(node{y1,y2,-1});
}
void push(int i,int l,int r) //扫描线
{
if(q[i].s)
q[i].len =r-l+1;
else if(l==r)
q[i].len =0;
else
q[i].len =q[i<<1].len +q[i<<1|1].len ;
}
void date(int i,int l,int r,int xx) //线段树
{
if(ql<=l && r<=qr)
{
q[i].s +=xx;
push(i,l,r);
return;
}
int mid=(l+r)>>1;
if(ql<=mid)
date(i<<1,l,mid,xx);
if(qr>mid)
date(i<<1|1,mid+1,r,xx);
push(i,l,r);
}
void get(int i,int l,int r) //找第二维度的值
{
if(q[i].len ==0)
{
printf("%d\n",l);
return;
}
int mid=(l+r)>>1;
if(q[i<<1].len <mid-l+1)
get(i<<1,l,mid);
else
get(i<<1|1,mid+1,r);
}
int main()
{
scanf("%d %d",&n,&d);
for(int i=1;i<=n;i++)
{
int x1,y1,x2,y2;
scanf("%d %d %d %d",&x1,&y1,&x2,&y2);
--x2,--y2;
if(x2-x1+1>=d)
x1=0,x2=d-1;
if(y2-y1+1>=d)
y1=0,y2=d-1;
x1=(x1%d+d)%d; //取模
x2=(x2%d+d)%d;
y1=(y1%d+d)%d;
y2=(y2%d+d)%d;
if(x1<=x2) //情况
{
if(y1<=y2)
add(x1,x2,y1,y2);
else
add(x1,x2,0,y2),add(x1,x2,y1,d-1);
}
else
{
if(y1<=y2)
add(0,x2,y1,y2),add(x1,d-1,y1,y2);
else
add(0,x2,0,y2),add(x1,d-1,0,y2),add(0,x2,y1,d-1),add(x1,d-1,y1,d-1);
}
}
for(int i=0;i<d;i++) //扫描
{
for(int j=0;j<v[i].size() ;j++)
{
ql=v[i][j].l;
qr=v[i][j].r;
date(1,0,d-1,v[i][j].x);
}
if(q[1].len <d)
{
printf("YES\n%d ",i);
get(1,0,d-1);
return 0;
}
}
printf("NO\n");
}
总结
这道题知道知识点的人都写得很快,基本半个小时就过了,思想很朴素,知识点需要巩固一下。
没学过扫描线的人可以看图理解一下(虽然有点丑)。