牛客网暑期ACM多校训练营(第二场)C 二分 + 凸包

题目链接


题意:
给定 n n n条斜率不为0的直线的参数 a a a, b b b y = a x + b y = ax + b y=ax+b),有 m m m次询问,每次给出一条直线的参数 c c c, d d d( y = c x + d y = cx + d y=cx+d),该直线与这 n n n条直线的交点中,横坐标最大为多少?


思路:

对于两条直线 y = a x + b y = ax + b y=ax+b y = c x + d y = cx + d y=cx+d,其交点的横坐标为:
x = − b − d a − c x = - \frac{b - d}{a - c} x=acbd

等价为两个点 ( a , b ) (a,b) (a,b) ( c , d ) (c,d) (c,d)斜率的相反数。

故该问题可转化为:求出给定一点与平面 n n n个点的斜率最小值。

此时假设我们正在考虑的询问点为 ( c , d ) (c, d) (c,d),则以直线 x = c x = c x=c,将平面上的 n n n个点分成两部分,而与询问点组成斜率最小的点,在左半区一定在尽量上面的地方,而右半区则尽量在下面的地方,且一定在最外围,即这 n n n个点的凸包顶点上。
此时容易发现,我们只需要在左半区维护一个上凸壳,在右半区维护一个下凸壳,对凸壳上的点进行二分即可得到答案。

此题得解。


代码:

#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<algorithm>
using namespace std;
typedef long long ll;
typedef long double ld;

const double eps = 1e-8;
const int A = 5e4 + 10;
class P{
public:
    ll x,y;
    int id;
    P(ll _x = 0, ll _y = 0, int _id = 0) {
        x = _x; y = _y; id = _id;
    }
    bool operator < (const P & rhs) const {
        return x < rhs.x;
    }
    P operator - (const P& rhs) const {
        return P(x - rhs.x, y - rhs.y);
    }
    ll operator * (const P& rhs) const {
        return x * rhs.y - y * rhs.x;
    }
    ld operator / (const P& rhs) const {
        if(x == rhs.x) return 0.0;
        return ld(y - rhs.y) / (x - rhs.x);
    }
}a[A], b[A], Sta[A];
int n, m, tot, j;
ld Ans[A];

ld Bi_search(P &x){
    int l = 1, r = tot;
    while (l < r) {
        int mid = (l+r)>>1;
        if (Sta[mid]/x > Sta[mid+1]/x) l = mid + 1;
        else                           r = mid;
    }
    return Sta[l] / x;
}

void solve(){
    j = 1;tot = 0;
    for (int i = 1; i <= m; i++) {
        while (j <= n && a[j].x < b[i].x) {
            while (tot >= 2 && (Sta[tot] - Sta[tot-1]) * (a[j] - Sta[tot]) >= 0) tot--;
            Sta[++tot] = a[j];
            j++;
        }
        if(tot == 0) Ans[b[i].id] = 1;
        else         Ans[b[i].id] = Bi_search(b[i]);
    }
    j = n;tot = 0;
    for (int i = m; i >= 1; i--) {
        while (j && a[j].x > b[i].x) {
            while (tot >= 2 && (Sta[tot] - Sta[tot-1]) * (a[j] - Sta[tot]) >= 0) tot--;
            Sta[++tot] = a[j];
            j--;
        }
        if(tot) Ans[b[i].id] = min(Ans[b[i].id], Bi_search(b[i]));
    }
}

int main(){
    scanf("%d", &n);
    for (int i = 1; i <= n; i++) {
        scanf("%lld%lld", &a[i].x, &a[i].y);
        a[i].id = 0;
    }
    scanf("%d", &m);
    for (int i = 1; i <= m; i++) {
        scanf("%lld%lld", &b[i].x, &b[i].y);
        b[i].id = i;
    }
    sort(a + 1, a + 1 + n);
    sort(b + 1, b + 1 + m);
    solve();
    for (int i = 1; i <= m; i++) {
        if(Ans[i] <= 0) printf("%.15f\n",-double(Ans[i]));
        else            printf("No cross\n");
    }
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值