20杭电多校1 1009 Leading Robots

题目分析

在这里插入图片描述
题目大意是这样的,有n个机器人,每个机器人有初始位置和加速度,一开始所有的机器人都会向右做加速运动。任务是统计总共多少个机器人排过第一名(严格意义的第一名不能是并列)

简单分析一下,高中一年级物理课我们学过,位移在匀加速直线运动条件下随时间变化的函数是

x = at2/2。
考虑到每个机器人有一个初始位置pi,则第i个机器人的位移应表示为:
xi = ait2/2 + pi

有了这个式子,我们知道机器人位移随时间的变化曲线是一条开口向上的抛物线,其纵截距是pi。但是我们不喜欢抛物线,或者说,他不够直观。那么不妨将t2看作是自变量而非t,使用字母u来表示,即:
u = t2
xi = aiu/2 + pi

由于t2与t在本题定义域[0,+∞)上的单调性相同,所以这样的替换并不会影响答案统计。

其实这也可以体现一个问题,我们使用了u代替了t2,从形式上看是将式子从二次降为一次,这与匀速直线运动的形式相同。更为确切的说,我们把匀加速直线运动用匀速直线运动代替了。按照这个思想,如果将加速度改为急动度,思路还是一样没有变化,只是平方要改成三次方,只要单调性相同,都不会影响答案的统计。


单调栈

简单分析完问题后,这就来想办法搞定这道题。

经过思考,可以得出下面一些结论:

  • 越排在前面的机器人越有可能成为第一。首先需要对所有机器人按照位置排序。
  • 由于是匀加速直线运动,最多只有一次相遇的机会,也就是后面的追上前面的。
  • 一个机器人能成为第一,需要满足三个条件:
    1.没有与之并行的
    2.在其前面没有比它加速度更大或相等的
    3.在其后面没有可以在它成为第一之前超过或追赶上它的

为了使用上面的几个条件,我们需要判断一个机器人是否能够超越前面的机器人,如果能够成为第一,那么需要多长时间。

最朴素的思想是遍历前面的机器人,如果加速度存在不小于该机器人的,则它不能成为第一。否则就可以,时间应该是超过所有机器人中最晚的那个。

但是这个算法存在一个非常严重的问题,就是复杂度太高,这样处理的复杂度将会是O(n2),对于105的数据规模,超时是必然的。除此之外,它还无法解决第三个条件,也就是不能判断后面的机器人是否会在它成为第一之前超过他。所以这个方法就顺理成章的被pass掉了。

因此我们需要改进统计的方法,至少应该满足两个条件,第一是复杂度,需要优化到O(nlogn)或者O(n),第二是支持对历史答案的记录并且在添加新的机器人时对现有的答案进行修正,及时删除掉会被超过的历史答案。

维护一个单调栈可以解决这个问题!这个单调栈中机器人的顺序就是当第一的顺序,加速度、和成为第一的时间也一定是单调的。

如果一个新的机器人想要成为第一,那么首先它的加速度必须大于单调栈中的最后一项,其次需要从这个单调栈的末尾开始比较起,如果它超越上一个元素的时间不晚于上一个元素成为第一的时间,那么易得上一个元素一定不会成为第一名,弹栈即可。

计算两个机器人相遇的时间,对于两个机器人i和j,我们假设pi < pj 且 ai > aj,就是让
xi = xj

aju/2 + pj = aiu/2 + pi
整理得
u = 2(pj - pi)/(aj - ai)

这里的2在计算时可以省略,不影响结果。

往复执行这个操作直到单调栈中不再有其他的元素或者不能在上一元素成为第一之前超越(包括时间不够早或者加速度不够大)。

最终答案就是单调栈中所有不存在并行的元素数量。并行情况可在拍完序后统计。最终复杂度:O(TNlogN)

代码如下:

#include<iostream>
#include<algorithm>
#define N 50050
using namespace std;
struct node{
	int a,p;
	
	bool operator==(const node & x){
		return x.a == a && x.p == p;
	}
}robot[N];
double time[N];
int stack[N];
int T,n;
bool equ[N];//是否存在并行

bool cmp(const node& a,const node& b){
	return a.p > b.p;
}

/*a追b*/
bool calc(int idxA,int idxB){
	node a = robot[idxA];
	node b = robot[idxB];
	/*加速度不够,追不上*/
	if(a.a <= b.a){
		return false;
	}
	
	double x = b.p - a.p;
	double accl = a.a - b.a;
	time[idxA] = x / accl;
	return time[idxA] <= time[idxB];
}

int main(){
	cin >> T;
	int top,ans;
	while(T--){
		cin >> n;
		for(int i = 0;i < n;i++){
			cin >> robot[i].p >> robot[i].a;
			equ[i] = true;
		}
		sort(robot,robot + n,cmp);//排个序
		/*标记并行*/
		for(int i = 0;i < n;i++){
			if(i && robot[i] == robot[i - 1]){
				equ[i] = equ[i - 1] = false;
			}
		}
		
		top = 0;
		
		for(int i = 0;i < n;i++){
			time[i] = 0;
			if(top && robot[i].a <= robot[stack[top]].a) continue;//是否可能成为第一
			
			stack[++top] = i;
			while(top != 1 && calc(i,stack[top - 1])) {//如果前面还有且可以抢占第一
				stack[top - 1] = stack[top];
				top--;
			}
		}
		
		ans = 0;
		/*统计答案(不存在并行)*/
		while(top){
			ans += equ[stack[top--]];
		}
		cout << ans << endl;
		
	}
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值