题目描述
有一天,DD_BOND被zmw气死了,于是决定拿C4炸了他的实验室座位。善良的DD_BOND不想伤及无辜的acmer,可是总有人会被波及到,于是DD_BOND想了一个绝妙的方法。把实验室看做无限大的二维平面,zwm的座位和其他acmer的位置描述为一个点,zmw的座位用点(sx,sy)
描述,其他acmer的位置用点(x,y)
表示,当DD_BOND开始说我要准备炸了的时候,大家会开始沿着各自固定方向逃跑,用方向向量(dx,dy)
表示每秒点的坐标变化量,例如一开始在点(0,0)
,方向向量(-1,2)
,那么经过2
秒后,点会处在(-2,4)
位置。DD_BOND用一个权衡值w来决定是否引爆C4,每个人所产生的权衡贡献是每个人距离zmw座位的曼哈顿距离决定(曼哈顿距离为两点之间x的坐标差值加上y的坐标差值的和),当所有人的权衡贡献之和不小于w
的时候,DD_BOND就可以引爆C4,那么现在来考考你,距离DD_BOND说开始炸了的时候,最早什么整数时刻可以引爆C4并满足引爆条件。
输入
第一行n,sx,sy,w
表示一共有n
个acmer、zmw的座位位置和权衡值w
。接下里n
行,每行包括x[i],y[i],dx[i],dy[i]
表示一开始每个acmer的位置和他们的方向向量。(1<=n<=2e5
,-1e9<=sx,sy,x[i]
,y[i],dx[i]
,dy[i]<=1e9
,0<=w<=1e18
,数据保证|dx[i]+dy[i]|>0
)
输出
输出一个正整数,满足引爆条件的最早时间。
样例输入
【样例1输入】
2 0 0 100
0 0 -1 0
0 0 1 0
【样例2输入】
1 0 0 10000000000000000
1000000000 0 -1 0
【样例3输入】
5 10 10 10000
1 2 -4 5
3 5 3 3
10 10 0 9
8 19 5 -4
5 1 -5 -3
样例输出
【样例1输出】
50
【样例2输出】
10000001000000000
【样例3输出】
245
一、题意概括
给定n,sx,sy,w
然后给定n
个坐标x[i],y[i]
和对应的方向向量dx[i],dy[i]
每秒钟,每个坐标依据方向向量变化,即
x[i]+=dx[i]
y[i]+=dy[i]
要求计算这n
个坐标在什么时候,距离点(sx,sy)
的曼哈顿距离之和大于等于w
*曼哈顿距离:两点的|x2-x1|+|y2-y1|
,即Δx+Δy
二、题目分析
首先,我们以点(sx,sy)
为原点重新建立坐标系,即
x[i]-=sx
y[i]-=sy
这样,曼哈顿距离就是每个点的坐标的绝对值之和
现在考虑位于y
轴右侧的点的横坐标变化
如果
x[i]>0,dx[i]>0
那么横坐标的绝对值|x|
单调递增
如果
x[i]>0,dx[i]<0
那么横坐标的绝对值|x|
先减后增
我们可以对单个点的横坐标变化(即曼哈顿距离)提取出函数关系式
设f(x)
为点的横坐标的绝对值,那么
f(t)=|dx*t+x|
其中,dx
为横坐标的方向向量,t
为时间(单位:秒),x
为初始横坐标
所以,初始情况下在y
轴右侧的点的水平方向上的曼哈顿距离之和为
F(t)=|dx1*t+x1|+|dx2*t+x2|+······+|dxm*t+xm|
其中,m
初始情况下在y
轴右侧的点的个数
《容易》 发现
在y轴左侧的点,只要翻转到右侧,同样遵循以上结论
点的x的变化,y的变化相互独立,可以解耦合,把竖直方向上的变化看作水平方向上的变化
现在,问题简化为:
F(t)-w>=0
成立的最小整数t
其中,
F(t)=|Dx1*t+X1|+|Dx2*t+X2|+······+|Dx2n*t+X2n|
2n
为点的个数的
两倍,
Dx
为
dx
或
dy
,
X
为对应的
x
或
y
我们发现,F(t)
是2n
个一元一次绝对值函数求和所构成的函数
对于这种函数,容易画出它的图像
所以我们需要分段处理,对每段t∈[l,r]
,求F(t)>=w
的最大整数t
注意,t
只能取整数,所以l,r
也只能取整数
接下来需要求每段l,r
的值
注意到,每次函数F(t)
发生转折,一定是F(t)
中的某个f(t)
由于绝对值的作用取了相反数
所以转折点的值是{F(t)
中的每个f(t)
值为0
时的}t
即每个f(t)
的零点
对于每个区间[l,r]
,l
的值是l
前面的转折点向上取整,r
的值是r
后面的转折点向下取整
如图,
第1个区间:l=0,r=1
第2个区间:l=2,r=4
第3个区间:l=4,r=6
第4个区间:l=7,r=+∞
在代码中,有以下注意点
- 在第一个区间,规定
l=0
- 在正好为整数的转折点上,把右区间的l向后移
1
位也不影响结果 - 在最后一个区间,规定
r=INF
,其中INF
为long long
的最大值
为了按照每个区间的先后顺序进行处理,我们需要分别求出每个f(t)
的零点t
,然后对其排序
有以下4
段代码共同实现这个功能
struct node{
LL x=0,dx=0,k=1;
LL t1=LLONG_MAX,t2=LLONG_MAX;
}a[maxn];
bool cmp(node x,node y){
return x.t1<y.t1;
}
for(int i=0;i<2*n;i++){
if(a[i].dx*a[i].x<0){//会产生转折点的情况
a[i].t1=-a[i].x/a[i].dx;//转折点左边的整数
a[i].t2=a[i].t1+1;//转折点右边的整数
}
}
sort(a,a+n*2,cmp);
现在,我们掌握了每段区间是因为哪个f(t)
的变化而发生转折的
因此,我们可以对每个f(t)
去绝对值
所以,
F(t)=Dx*t+X
其中,Dx
和X
是每个f(x)
去绝对值后的求和
现在我们从头开始走一遍程序
首先输入n,sx,sy,w
定义结构体a[]
,输入a[i].x
,a[i+n].x
,a[i].dx
,a[i+n].dx
(x
坐标和y
坐标等效,打乱输入即可)
判断t=0
时是否F(x)>=w
直接就成立,成立输出0
并结束函数,否则继续
对不会发生转折的点(单调递增),Dx+=abs(a[i].dx)
,X+=abs(a[i].x)
,a[i].t1=a[i].t2=INF(便于排序)
,后面直接忽略这些点(没用了)
对会发生转折的点(先减后增),求出转折点的左右两边a[i].t1=-a[i].x/a[i].dx
,a[i].t2=a[i].t1+1
,对在坐标轴左侧的点,标记需要翻转a[i].k*=-1;
然后按a[i].t1
升序排序 sort(a,a+n*2,cmp);
在第1个区间[0,r]
判断之前,按照标记需要翻转的情况,对每个f(x)
中的dx
和x
分别求和,加到Dx
和X
上
判断第1个区间,t=(w-X)/Dx
,如果求得t∈[0,r]
,输出t
,结束函数
第1个区间判断之后,用i
遍历每个排序后的点(f(x)
),每次循环处理1个区间[l,r],并翻转当前的点(取相反数)a[i].k*=-1
,先在Dx
和X
中减掉翻转之前的点,再加上翻转之后的点
if(a[i].dx*a[i].x<0){
dx2-=a[i].k*a[i].dx;
x2-=a[i].k*a[i].x;
a[i].k*=-1;
dx2+=a[i].k*a[i].dx;
x2+=a[i].k*a[i].x;
}
然后依照第1个区间的方法,算出t
,判断t
是否在区间[l,r]
内
注意:算t
时,如果除数为0
会运行错误,这时需要continue
进入下一个循环(意味着这个区间是平的)
以下是AC
代码
#include<iostream>
#include<cstring>
#include<algorithm>
#include<climits>
#define LL long long
const int maxn=4e5+10;
using namespace std;
LL n,sx,sy,w;
LL dx1=0,x1=0;
struct node{
LL x=0,dx=0,k=1;
LL t1=LLONG_MAX,t2=LLONG_MAX;
}a[maxn];
bool cmp(node x,node y){
return x.t1<y.t1;
}
void output(int n){//用于调试
cout<<endl;
for(int i=0;i<2*n;i++){
printf("i=%d x=%d dx=%d t1=%lld t2=%lld\n",i,a[i].x,a[i].dx,a[i].t1,a[i].t2);
}
cout<<endl;
}
void input(){//输入
cin>>n>>sx>>sy>>w;
for(int i=0;i<n;i++){
cin>>a[i].x>>a[i+n].x>>a[i].dx>>a[i+n].dx;
a[i].x-=sx;
a[i+n].x-=sy;
}
}
bool First(){//当 时间=0 时特判
LL ans=0;
for(int i=0;i<n*2;i++){
ans+=abs(a[i].x);
if(ans>=w){
cout<<0;
return true;
}
}
return false;
}
void init(){//初始化
for(int i=0;i<2*n;i++){
if(a[i].dx*a[i].x<0){
a[i].t1=-a[i].x/a[i].dx;
a[i].t2=a[i].t1+1;
if(a[i].x<0) a[i].k*=-1;
}
if(a[i].dx*a[i].x>0){
dx1+=abs(a[i].dx);
x1+=abs(a[i].x);
}
if(a[i].x==0) dx1+=abs(a[i].dx);
if(a[i].dx==0) x1+=abs(a[i].x);
}
// output(n);
sort(a,a+n*2,cmp);
}
int main(){
input();
if(First()) return 0;//时间为0
init();
LL dx2=dx1,x2=x1,t,l,r;
for(int j=0;j<n*2;j++){
if(a[j].dx*a[j].x<0){
dx2+=a[j].k*a[j].dx;
x2+=a[j].k*a[j].x;
}
}
if((w-x2)%dx2==0) t=(w-x2)/dx2;
else t=(w-x2)/dx2+1;
l=0;r=a[0].t1;
if(t>=0 and t<=r){
cout<<t;
return 0;
}
for(int i=0;i<n*2+2;i++){
l=a[i].t2;
r=a[i+1].t1;
// cout<<l<<" "<<r<<endl; 用于调试
if(a[i].dx*a[i].x<0){
dx2-=a[i].k*a[i].dx;
x2-=a[i].k*a[i].x;
a[i].k*=-1;
dx2+=a[i].k*a[i].dx;
x2+=a[i].k*a[i].x;
}
if(dx2==0) continue;
if((w-x2)%dx2==0) t=(w-x2)/dx2;
else t=(w-x2)/dx2+1;
if(t>=l and t<=r){
cout<<t;
return 0;
}
}
return 0;
}