bzoj3190 [JLOI2013]赛车(半平面交+单调栈)

题目链接

分析:
在任一时刻 t t ,赛车i的位置为: gi+vit g i + v i t
我们可以得到若干形如上式的方程,对于每一辆赛车,询问是否存在一 t t 使得gi+vit最大

一次函数形式,如果我们把这些式子画到二维平面上,就得到若干直线
这里写图片描述
青色(淡蓝色,水蓝色,管你什么蓝色)的线标出来的就是获奖赛车
显然,我们只需要把这 n n 条直线求一个半平面交,半平面上的点在哪条直线上,哪辆赛车就可以获奖

我一开始naive求一个半平面交,之后暴力判断点是否在直线上
赤裸裸的T啊


我们把直线按照v排序,( v v 相同的直线,g大的排名靠前)把这些直线都扔到一个单调栈
我们每次插入一条直线,那么什么样的直线才是合法的呢(出栈条件是什么)?
这里写图片描述
现在栈里已经有了红线和橙线
如果插入蓝线,我们发现ta和 S[top] S [ t o p ] 的交点较靠上,直接入栈即可
如果插入绿线,我们发现ta和 S[top] S [ t o p ] 的交点较靠下,所以我们要把 S[top] S [ t o p ] 弹出,再插入绿线
也就是说我们可以通过和栈顶两条直线的交点位置判断是否要弹栈

但是平行直线是没有办法求交点的
然而因为我们在相同斜率的情况下优先插入b值大的直线
所以如果遇到斜率相同的直线直接忽略即可

在最后统计答案的时候,如果两条直线重合,那么这两辆赛车都可以获奖
还有一个小细节:
这里写图片描述

我们在最后维护答案的时候,有可能出现 S[1] S [ 1 ] S[2] S [ 2 ] 这样的情况
也就是说,如果平面包括第二象限,那么这两条直线是合法的
但是我们的图像只能在第一和第四象限
因此我们在最后需要特殊判断一下:栈中相邻的两条直线的交点横坐标是否大于零

tip

这里写图片描述
不懂,人懒不想改

#include<cstdio>
#include<cstring>
#include<iostream>
#include<cmath>
#include<algorithm>

using namespace std;

const double eps=1e-7;
const int N=10010;
struct node{
    double k,b;
    int id,num;
    bool operator <(const node &a) const{
        return (k<a.k)||(k==a.k&&b>a.b);
    }
};
node l[N],S[N];
int n,m,top=0,ans[N];
bool vis[N];

int dcmp(double x) {
    if (fabs(x)<eps) return 0;
    else if (x>0) return 1;
    else return -1;
}

double jiao(node l1,node l2) {
    return (l2.b-l1.b)/(l1.k-l2.k);
}

int check(node l1,node l2,node L) {
    double x=jiao(l1,l2);
    double t1=l1.k*x+l1.b;
    double t2=l2.k*x+l2.b;
    return dcmp(t1-t2)<0;
}

void solve() {
    for (int i=1;i<=n;i++) {
        if (top&&dcmp(l[i].k-S[top].k)==0) continue;    
        //斜率相同的情况下我们插入的是b最大的直线
        while (top>1&&check(S[top],S[top-1],l[i])) top--;
        S[++top]=l[i];
    }
    for (int i=1;i<=top;i++) {
        if (i!=top) {
            double x=jiao(S[i],S[i+1]);
            if (dcmp(x)<0) continue;
            //交点横坐标必须为正 
        }
        node now=S[i];
        for (int i=now.num;i<=n;i++)
            if (dcmp(now.k-l[i].k)==0&&dcmp(now.b-l[i].b)==0) //平行直线都可以获胜
                vis[l[i].id]=1; 
            else break;
    }
}

int main()
{
    scanf("%d",&n);
    for (int i=1;i<=n;i++) scanf("%lf",&l[i].b),l[i].id=i;
    for (int i=1;i<=n;i++) scanf("%lf",&l[i].k);

    sort(l+1,l+1+n);
    for (int i=1;i<=n;i++) l[i].num=i;
    solve();

    for (int i=1;i<=n;i++) 
        if (vis[i]) ans[++ans[0]]=i;
    printf("%d\n",ans[0]);
    for (int i=1;i<=ans[0];i++)
        printf("%d%c",ans[i],i==ans[0]? '\n':' '); 
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值