2020杭电暑期多校01 09 - Leading Robots (HDU6759) 凸包

20200723005910

2020杭电暑期多校01 09 - Leading Robots (HDU6759) 凸包

一、题意

N N N 个机器人在一维数轴上向正方向赛跑。

已知 t = 0 t=0 t=0 时刻时第 i i i 个机器人的位置为 p i p_i pi、初速度为零、加速度始终为 a i a_i ai

问,在无限长的比赛时间内,有多少个机器人能够有绝对领先的时候,即,它至少在某一时刻能在所有其它机器人的前面(坐标绝对大于其它机器人)?

样 例 数 T ≤ 50 ; 1 ≤ N ≤ 5 ∗ 1 0 4 ; 1 ≤ p i , a i ≤ 2 31 ; 样例数 T\leq 50; 1\leq N\leq 5*10^4;1\leq p_i,a_i\leq 2^{31}; T50;1N5104;1pi,ai231;

可能有多个机器人的初始位置、加速度完全一致。

二、题解

以加速度为横坐标、初始位置为纵坐标(二者互换也可),将第 i i i 个机器人表示为二维平面上的一个点 S i ( a i , p i ) S_i(a_i,p_i) Si(ai,pi)

先比较两机器人 i , j i,j i,j

  • KaTeX parse error: Undefined control sequence: \and at position 15: (a_i\geq a_j) \̲a̲n̲d̲ ̲(p_i> p_j),显然 i i i 永远在 j j j 前;
    • 表现在图上即 S i S_i Si 点在 S j S_j Sj 点右上方或正上方
    • j j j 没有希望绝对领先了, i i i 完全不受 j j j 的限制
  • KaTeX parse error: Undefined control sequence: \and at position 10: (a_i>a_j)\̲a̲n̲d̲ ̲(p_i==p_j),除 t = = 0 t==0 t==0 时刻两者并排,以后 i i i 永远在 j j j 前;
    • 表现在图上即 S i S_i Si 点在 S j S_j Sj 点正右方
    • j j j 没有希望绝对领先了, i i i 完全不受 j j j 的限制
  • KaTeX parse error: Undefined control sequence: \and at position 11: (a_i==a_j)\̲a̲n̲d̲(p_i==p_j),两者永远并排跑;
    • 表现在图上即 S i S_i Si S j S_j Sj 重合
    • 两者都没有希望绝对领先了
  • 只有当一个机器人的初始位置大、另一个的加速度大时,它们才会出现中途超越的现象;
    • 表现在图上即两点呈连线斜率为负的关系
    • 设超越时刻为 t 0 t_0 t0,则 p i + 1 2 a i t 0 2 = p j + 1 2 a j t 0 2 p_i+\frac{1}{2} a_i t_0^2=p_j+\frac{1}{2} a_j t_0^2 pi+21ait02=pj+21ajt02,解得 KaTeX parse error: Undefined control sequence: \root at position 5: t_0=\̲r̲o̲o̲t̲\of{\frac{2(p_i…,注意这一值与两点连线斜率的绝对值相关,设斜率绝对值为 ∣ k ∣ |k| k,则 KaTeX parse error: Undefined control sequence: \root at position 5: t_0=\̲r̲o̲o̲t̲\of{2|k|}
      • a i > a j a_i>a_j ai>aj,则 i i i 绝对领先 j j j 的时段为 ( t 0 , + ∞ ) (t_0,+\infin) (t0,+)
      • a j < a j a_j<a_j aj<aj,则 i i i 绝对领先 j j j 的时段为 [ 0 , t 0 ) [0,t_0) [0,t0)

根据上述分析,对于机器人 i i i

  1. 首先其对应点 S i S_i Si 右上方、正右方、正上方、自己重合处都不能有点,它才有可能有绝对领先的时候;

  2. 如果满足上述条件,那么其左下方、正左方、正下方的点对它都没有限制,他只需关注左上方、右下方的点;

  3. 左上方的点限制了 i i i 绝对领先的开始时间,为了让其尽可能小,它们与 S i S_i Si 的连线斜率绝对值的最大值 ∣ k 1 ∣ |k_1| k1 要尽可能小,因此连线斜率尽可能平缓;右下方的点限制了 i i i 绝对领先的结束时间,为了让其尽可能大,它们与 S i S_i Si 的连线斜率绝对值的最小值 ∣ k 2 ∣ |k_2| k2 要尽可能大,因此连线斜率尽可能陡峭。只有使 ∣ k 1 ∣ < ∣ k 2 ∣ |k_1|<|k_2| k1<k2 i i i 才能在 KaTeX parse error: Undefined control sequence: \root at position 2: (\̲r̲o̲o̲t̲\of{2|k_1|},\ro… 时间内绝对领先所有人。

于是,就转变成凸包问题了(画画图即可知),要求出凸包壳的右上部分的其中一些点即代表有绝对领先时刻的机器人。

本题需要特别注意重合点的处理:一是运行求凸包算法时通常需要用三角形面积正负来判断出栈,重合点会造成面积为零;二是对本题来说,绝对领先的定义要求重合点不可计入答案。

三、AC代码

#include<cstdio>
#include<algorithm>
//#include<stack>
#define ll long long

struct Point{
    ll x;
    ll a;
    bool multi;
    int multi_id;
}point[50004];

int cmp(Point p1 , Point p2){
    if(p1.multi_id != p2.multi_id) return ((p1.multi_id)?0:1);
    if(p1.x*p2.a == p1.a*p2.x) return (p1.a<p2.a);
    return (p1.x*p2.a > p1.a*p2.x);
}

ll area(int i , int j , int k){
    return (point[j].a-point[i].a)*(point[k].x-point[i].x) - (point[j].x-point[i].x)*(point[k].a-point[i].a);
}

//std::stack<int> sta;
int sta[50004];
int sta_top=0;
void sta_push(int v){ sta[sta_top++]=v; }

int main(){
    int T;
    scanf("%d" , &T);
    while(T--){
        int n;
        scanf("%d" , &n);
        for(int i=0 ; i<n ; ++i) scanf("%lld %lld" , &point[i].x , &point[i].a);
        for(int i=0 ; i<n ; ++i) point[i].multi=false;
        for(int i=0 ; i<n ; ++i) point[i].multi_id=0;  // added

        std::sort(point , point+n , cmp);
        //for(int i=0 ; i<n ; ++i) printf("[%d] : a=%lld , x=%lld\n" , i , point[i].a , point[i].x);

        for(int i=0 ; i<n ; ++i){
            if(i+1<n && point[i].a==point[i+1].a && point[i].x==point[i+1].x){
                point[i].multi=true;
                point[i+1].multi=true;
                point[i].multi_id=1;  // not 0
                point[i+1].multi_id=0;  // not 1
            }
        }
        std::sort(point , point+n , cmp);
        //for(int i=0 ; i<n ; ++i) printf("[%d] : a=%lld , x=%lld , multi=%d , multi_id=%d\n" , i , point[i].a , point[i].x , point[i].multi , point[i].multi_id);
        while(n>0 && point[n-1].multi_id==1) --n;
        //printf("n=%d\n" , n);

        sta_top=0;
        for(int i=0 ; i<n ; ++i){
            while(sta_top>=2 && area(sta[sta_top-2] , sta[sta_top-1] , i)>0) --sta_top;
            sta_push(i);
        }
        //for(int i=0 ; i<sta_top ; ++i) printf("sta[%d]=%d , a=%lld , x=%lld , multi=%d\n" , i , sta[i] , point[sta[i]].a , point[sta[i]].x , point[sta[i]].multi);

        int L=0;
        while(L+1<sta_top && point[sta[L]].x <= point[sta[L+1]].x) ++L;
        int R=sta_top-1;
        while(R-1>=0      && point[sta[R]].a <= point[sta[R-1]].a) --R;
        //printf("L=%d , R=%d\n" , L , R);

        int ans=0;
        for(int i=L ; i<=R ; ++i){
            if(i>0 && i<sta_top-1)
                if( area(sta[i-1] , sta[i] , sta[i+1])>=0 ) continue;
            if(point[sta[i]].multi) continue;
            ++ans;
            //printf("lead : sta[i=%d]=%d : a=%lld , x=%lld\n" , i , sta[i] , point[sta[i]].a , point[sta[i]].x);
        }

        printf("%d\n" , ans);
    }
    return 0;
}

/*

1
9

2 1
4 2
6 3
3 3
7 4
7 5
6 6
4 8
1 9


1
7
15 6
8 4
7 7
11 18
4 20
4 20
1 20


1
9
20 19
20 12
15 7
20 7
15 3
20 1
12 6
15 3
5 19


*/

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值