牛客2021暑期训练6-H-Hopping Rabbit
题意
平面上有 n n n个矩形,给定 d d d,需要找一个位置 ( x , y ) (x,y) (x,y),使得所有 ( x + k d , y + k d ) (x+kd,y+kd) (x+kd,y+kd)均不落在矩形中
题解
前置知识:扫描线
由于能到的位置是重复的,我们将所有矩形移到
(
0
,
0
)
(0,0)
(0,0)到
(
d
,
d
)
(d,d)
(d,d)范围内求并,如果最终范围内存在未被覆盖的点,则该点可作为答案。矩形求并,用扫描线即可。
移动到
(
d
,
d
)
(d,d)
(d,d)范围内可能拆成
1
,
2
,
4
1,2,4
1,2,4个矩形
同时由于题目给的是矩形,而小兔子是在矩形中点,因此矩形的
x
2
x_2
x2是可以踩的,我们将边也移到中点去,
x
1
,
x
2
x_1,x_2
x1,x2就变成
x
1
,
x
2
−
1
x_1,x_2-1
x1,x2−1,
y
y
y同理。
其他东西代码里有解释
代码
#include<bits/stdc++.h>
#define PR pair<int,int>
#define Fi first
#define Se second
#define Mp make_pair
#define Pb push_back
using namespace std;
const int N=1e5+9;
int n,d;
int t[N*4],vis[N*4];
vector<PR>e1,e2,s1[N],s2[N];
void Change(int u,int l,int r,int y1,int y2,int flag)
{
if(y1<=l&&r<=y2)
{
t[u]+=flag;
vis[u]+=flag; //vis表示区间l~r的矩阵个数
return;
}
int mid=(l+r)/2;
if(y1<=mid) Change(u*2,l,mid,y1,y2,flag);
if(y2>mid) Change(u*2+1,mid+1,r,y1,y2,flag);
t[u]=min(t[u*2],t[u*2+1])+vis[u];
}
int Ask(int u,int l,int r)
{
if(l==r) return l;
int mid=(l+r)/2;
if(t[u]==t[u*2]+vis[u]) return Ask(u*2,l,mid); //23行代码
else return Ask(u*2+1,mid+1,r);
}
int main()
{
cin>>n>>d;
int P=(1<<30)/d*d; //小于等于 1<<30 的最大d的倍数
for(int i=1;i<=n;i++)
{
int x1,x2,y1,y2;
cin>>x1>>y1>>x2>>y2;
x1+=P; y1+=P; x2+=P; y2+=P; //加上d的倍数保证为正数
e1.clear(); e2.clear();
if(x2-x1>=d) e1.Pb(Mp(0,d-1)); //塞满d
else if(x1%d<=(x2-1)%d) e1.Pb(Mp(x1%d,(x2-1)%d)); //x未超过d
else //图中超过d,分成两个x
{
e1.Pb(Mp(x1%d,d-1));
e1.Pb(Mp(0,(x2-1)%d));
}
if(y2-y1>=d) e2.Pb(Mp(0,d-1));
else if(y1%d<=(y2-1)%d) e2.Pb(Mp(y1%d,(y2-1)%d));
else
{
e2.Pb(Mp(y1%d,d-1));
e2.Pb(Mp(0,(y2-1)%d));
}
//只拆成一个矩形:一个x,一个y
//拆成两个:一个x两个y,或两个x一个
//拆成四个:两个x,两个y
for(auto j:e1) //平行y的扫描线
{
for(auto k:e2)
{
s1[j.Fi].Pb(k); //前一条y为+1
s2[j.Se+1].Pb(k); //j.Fi~j.Se为+1,x=j.Se+1减掉
}
}
}
for(int i=0;i<d;i++) //扫描线
{
for(auto j:s1[i])
Change(1,0,d-1,j.Fi,j.Se,1);
for(auto j:s2[i])
Change(1,0,d-1,j.Fi,j.Se,-1);
if(!t[1]) //若t1==0,说明没有一条线覆盖0~d-1且存在一点为0
{
printf("YES\n%d %d\n",i,Ask(1,0,d-1));
return 0;
}
}
printf("NO\n");
return 0;
}