2020年浙江理工大学新生赛 F DD_BOND定点轰炸

三年OI一场空

不开LL见祖宗

题目描述
有一天,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为点的个数的 两倍DxdxdyX为对应的 xy

我们发现,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,其中INFlong 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

其中,DxX是每个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)中的dxx分别求和,加到DxX
判断第1个区间,t=(w-X)/Dx,如果求得t∈[0,r],输出t,结束函数
第1个区间判断之后,用i遍历每个排序后的点(f(x)),每次循环处理1个区间[l,r],并翻转当前的点(取相反数)a[i].k*=-1,先在DxX中减掉翻转之前的点,再加上翻转之后的点

	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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值