LA 4253 Archery (暴力枚举/二分答案+枚举)

本文探讨了一个涉及射击游戏的算法问题,即如何判断在特定区域内是否存在一个位置,从该位置发射的箭矢能穿过所有目标靶子。文章提供了两种解决方案:一种是通过枚举每两个靶子来计算可行区间的O(n^2)方法;另一种是采用二分搜索结合角度判断的O(nlogn)高效算法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

LA 4253 Archery

题目大意:

有n个与x轴平行的线段,每条线段是一个靶子(由D,L,R表示纵坐标为D,左右端点横坐标为L,R),问在x轴的[0,W]区间上是否存在位置可以使得箭穿过所有靶子.假设箭沿直线飞行,直到无穷远处,不同靶子纵坐标不同.

题目分析:

解法一(O(n^2)):

将靶子两两进行枚举,判断i靶子左/右端点与j靶子右/左端点在x轴上的交点形成区间与原区间取交集,若为空,无解.

解法一代码:
#include<cstdio>
#include<iostream>
#include<algorithm>

using namespace std;

const int maxn=50000+10;

int D[maxn],L[maxn],R[maxn];

double pos(double xi,double yi,double xj,double yj)//计算两点确定的某线段与x轴交点横坐标 
{
    return xj-yj*(xi-xj)/(yi-yj);
}

void work(int i,int j,double& l,double& r)//计算可行区间 
{
    if(D[i]<D[j]) swap(i,j);
    l=pos(R[i],D[i],L[j],D[j]);
    r=pos(L[i],D[i],R[j],D[j]);
}

int W,n;

bool check()//O(n^2)枚举每两条线段,计算可行区间 
{
    double l=0,r=W,ll,rr;
    for(int i=0;i<n;i++)
        for(int j=i+1;j<n;j++) {
            work(i,j,ll,rr);
            l=max(l,ll);r=min(r,rr);
            if(l>r) return false;
        }
    return true;
}

int main()
{
    int T;
    scanf("%d",&T);
    while(T--) {
        scanf("%d%d",&W,&n);
        for(int i=0;i<n;i++) scanf("%d%d%d",&D[i],&L[i],&R[i]);
        printf("%s\n",check()?"YES":"NO");
    }
    return 0;
}
解法二(O(nlogn)):

对于某一个点来说,若无法射到某一个靶子,那么就需要进行移动.
那么将该思路引申到二分答案,判断可依据斜率/角度,这里用atan2函数,依据角度判断.
(注:解法二需要先以高度排序,原因在于高和低,底和高的位置移动判断不同)

解法二代码:
#include<cmath>
#include<cstdio>
#include<iostream>
#include<algorithm>

using namespace std;

const double eps=1e-8;
const int maxn=50000+10;

struct Point {
    int d,l,r;
    bool operator < (const Point& rhs) const {
        return d<rhs.d;//需要按照高度排序,否则check中无法判断位置应当向那边移动 
    }
    void input() {
        scanf("%d%d%d",&d,&l,&r);
    }
}P[maxn];

int n;

int check(double pos)//0 可以射穿 1 往右移动 -1 往左移动 
{
    double l=atan2(P[0].d,P[0].l-pos);//atan2(y,x)得到(y,x)与(0,0)形成的夹角(弧度值) 
    double r=atan2(P[0].d,P[0].r-pos);
    for(int i=1;i<n;i++) {
        double ll=atan2(P[i].d,P[i].l-pos);
        double rr=atan2(P[i].d,P[i].r-pos);
        if(rr-l>eps) return 1;//若角度区间小了,往右移动 
        if(r-ll>eps) return -1;//若角度区间大了,往左移动 
        l=min(l,ll);r=max(r,rr);
    }
    return 0; 
}

bool solve(int W)
{
    double l=0,r=W;
    while(r-l>eps) {
        double mid=(l+r)/2;
        int k=check(mid);
        if(!k) return true;
        if(k>0) l=mid;
        else r=mid;
    }
    return false;
}

int main()
{
    int T,W;
    scanf("%d",&T);
    while(T--) {
        scanf("%d%d",&W,&n);
        for(int i=0;i<n;i++) P[i].input();
        sort(P,P+n);
        printf("%s\n",solve(W)?"YES":"NO");
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值