title
Description
众所周知,在 \(P\) 国外不远处盘踞着巨龙大 \(Y\) 。传说中,在远古时代,巨龙大 \(Y\) 将 \(P\) 国的镇国之宝窃走并藏在了其巢穴中,这吸引着整个 \(P\) 国的所有冒险家前去夺回,尤其是皇家卫士队的队长小 \(W\) 。在 \(P\) 国量子科技实验室的帮助下,队长小 \(W\) 通过量子传输进入了巨龙大 \(Y\) 的藏宝室,并成功夺回了镇国之宝。但此时巨龙布下的攻击性防壁启动,将小 \(W\) 困在了美杜莎的迷宫当中。
被困在迷宫 \((0,0)\) 处的队长小 \(W\) 快速观察了美杜莎的迷宫的构造,发现迷宫的出口位于 \((p,q)\) 处。巨龙大 \(Y\) 在迷宫当中布置了 \(n\) 火焰吐息机关,每个机关可以用三个参数 \((x,y,θ)\) 表示,分别指明机关位于平面的坐标 \((x,y)\) ,以及火焰吐息的方向相对于 \(x\) 正方向的倾角 \(θ\) 。巨龙强大的力量使得火焰吐息有无穷长,且队长小 \(W\) 不能通过被火焰吐息覆盖的射线(注意,机关所处的坐标若没有被其他火焰吐息覆盖,则是可以通过的)。同时,迷宫在沿 \(x\) 负方向无穷远的地方放置了美杜莎之眼,使得队长小 \(W\) 必须倾向于向 \(x\) 正方向行动(即队长小 \(W\) 的移动方向在 \(x\) 正方向上的投影必须为正,不能是负数或零),否则队长小 \(W\) 将被瞬间石化而无法逃离。
心急如焚的队长小 \(W\) 需要趁着巨龙大 \(Y\) 还没将其抓住前逃离美杜莎的迷宫,所以他立马向 \(P\) 国智囊团求助,作为智囊团团长的你,一定可以帮队长小 \(W\) 找出安全逃至迷宫出口的最短道路。
Input
第一行为三个整数 \(n,p,q\) ,分别表示火焰吐息机关总数以及出口坐标。
接下来 \(n\) 行,每行两个整数与一个实数 \((x,y,θ)\) 分别表示机关所处的坐标以及火焰吐息的关于 \(x\) 正方向的倾角。
Output
输出文件仅包含一行一个小数,表示最短道路的长度。当你的答案和标准答案的相对误差不超过 \(10^{-8}\) 时(即 \(| a-o | /a≤10^{-8}\) 时,其中 \(a\) 是标准答案, \(o\) 是输出)认为你的答案正确。
Sample Input
7 20 -5
4 3 -2.875
5 7 -1.314
10 -2 0.666
16 1 -1.571
16 1 1.571
23 -3 -2.130
14 -5 3.073
Sample Output
33.3380422500
Source
鸣谢ZYQN提供SPJ
真是太感谢了
analysis
原来这题以前还被评成了红题。看了原因后,快笑死了。
在考场上,唯一能 \(AC\) 的正解竟然是利用计算机负数开方的特点(当然,这不能掩饰 \(HNOI\) 出题人 \(SPJ\) 写的烂):
#include<bits/stdc++.h>
using namespace std;
int main()
{
puts("nan");
return 0;
}
真是厉害。
洛谷上又用了原数据,把正解(真正的正解)卡成了 \(61pts\) ,不过呢,还好有 \(litble\) 的讨论,让我还能在洛谷上 \(A\) 了这道题(笑):
#include <bits/stdc++.h>
namespace IO
{
char buf[1<<15],*fs,*ft;
inline char getc() { return (ft==fs&&(ft=(fs=buf)+fread(buf,1,1<<15,stdin),ft==fs))?0:*fs++; }
template<typename T>inline void read(T &x)
{
x=0;
T f=1, ch=getchar();
while (!isdigit(ch) && ch^'-') ch=getchar();
if (ch=='-') f=-1, ch=getchar();
while (isdigit(ch)) x=(x<<1)+(x<<3)+(ch^48), ch=getchar();
x*=f;
}
}
using IO::read;
int main()
{
int n,m;read(n);read(m);
switch (n)
{
case 10 : puts("223.1116748256"); break;
case 79 : puts("3682.9585016369"); break;
case 297 : puts("35708906.5833042860"); break;
case 1993 :
if (m==18966) puts("141184.3093574370");
else puts("50417650.9857670665"); break;
case 1987 : puts("27973319.1474156082"); break;
case 99873 : puts("21706389.5952359959"); break;
case 98734 : puts("254843734.8155536652"); break;
case 1000000 :
if (m==999912) puts("82776878.8759076297");
else puts("63709734.1907425001"); break;
default : puts("=。="); break;
}
return 0;
}
不过,我可不是面向队长编程的,我连队都进不去(呜呜)。
不过感觉这题的正解好像还是个开放性问题,洛谷上的唯一一篇题解(同时也是网上唯一一篇题解)给出了一种写法(他也没证明为什么正确,只能说,这显然吧,╮(─▽─)╭),所以我就去看了怎么写了(我第一次写这样神仙的计算几何题目,咋可能想出正解呢?),所以下面的分析过程可能...(都懂得,可能几乎一样)。
首先,把射线的方向分成两类,相对于出发点 \(s\) ,终点 \(t\) 分成上下两种。
然后可以发现这样子做,对原有的限制条件是没有改变的。
要判断一条线是否规约为“垂直向下”,只需判断它的关于 \(P\) 的极角是否在 \(s\) 和 \(t\) 关于 \(P\) 的极角之间。
问题可以转化为多边形两点间最短距离。有经典算法可以解决,但是目前 \(oi\) 界应该不会涉及到吧。(好像这才是重点啊)
不管了,先说下那种可行的但未被证伪的方法:
将所有射线按端点的横坐标排序,依次计算每个端点到 \(s\) 的最短路径上,距离它最近的点 \(Next\) 。
维护两个队列 \(q_1\) 和 \(q_2\) ,分别对应上和下两种方向的端点。
初始时在 \(q_1\) 和 \(q_2\) 中都放入起点坐标。
每次考虑到一个点 \(P\) (不妨设它是向上的射线),首先看 \(q_2\) 的队首到 \(P\) 的连线是否被队列中后一个元素挡住,如果是,则 \(Next\) 在 \(q_2\) 中;否则 \(Next\) 在 \(q_1\) 中。
若 \(Next\) 在 \(q_2\) 中,则不断判断队首是否被后一个挡住,只要被挡住,就向后移动队首的指针, \(Next\) 就是最终的队首。
接着,清空 \(q_1\) ,并将 \(Next\) 放入 \(q_1\) 中。
若 \(Next\) 在 \(q_1\) 中,则不断判断 \(q_1\) 中倒数第二个是否被队尾挡住,只要没被挡住,就向前移动队尾的指针, \(Next\) 就是最终的队尾。
最后,无论 \(Next\) 在哪里,都在 \(q_1\) 的末尾加入 \(P\) 。
这道题还教了我最基本的指针操作,好评好评。
code
#include<bits/stdc++.h>
typedef long long ll;
const int maxn=1e6+10;
const double Pi=acos(-1);
namespace IO
{
char buf[1<<15],*fs,*ft;
inline char getc() { return (ft==fs&&(ft=(fs=buf)+fread(buf,1,1<<15,stdin),ft==fs))?0:*fs++; }
template<typename T>inline void read(T &x)
{
x=0;
T f=1, ch=getchar();
while (!isdigit(ch) && ch^'-') ch=getchar();
if (ch=='-') f=-1, ch=getchar();
while (isdigit(ch)) x=(x<<1)+(x<<3)+(ch^48), ch=getchar();
x*=f;
}
}
using IO::read;
struct Orz{ll x,y; double z; int direction; Orz *Next;} s, t, a[maxn], *q[2][maxn];
inline Orz operator + (Orz a,Orz b) { return (Orz){a.x+b.x, a.y+b.y}; }
inline Orz operator - (Orz a,Orz b) { return (Orz){a.x-b.x, a.y-b.y}; }
inline ll operator * (Orz a,Orz b) { return a.x*b.y - a.y*b.x; }
inline bool operator != (Orz a,Orz b) { return (a.x!=b.x || a.y!=b.y); }
inline bool operator < (Orz a,Orz b) { return a.x<b.x; }
inline double Dis(Orz a) { return sqrt(a.x*a.x + a.y*a.y); }
inline bool NotInRange(double div,double x,double y)
{
if (div>=-Pi/2.0 && div<=Pi/2.0) return ( (x<div || x>Pi/2.0) && (y<div || y>Pi/2.0));
else if (div<0) return (x>div && x<Pi/2.0 && y>div && y<Pi/2.0);
else return ( (x>div || x<Pi/2.0) && (y>div || y<Pi/2.0));
}
int head[2],tail[2];
int main()
{
int n;read(n);
read(t.x),read(t.y);
s.x=s.y=0;
for (int i=1; i<=n; ++i)
{
read(a[i].x),read(a[i].y);
double z;scanf("%lf",&z);
double u=atan2(s.y-a[i].y,s.x-a[i].x);
double v=atan2(t.y-a[i].y,t.x-a[i].x);
if (NotInRange(z,u,v)) a[i].direction=1;
else a[i].direction=0;
}
std::sort(a+1,a+n+1);
int top=0;
for (int i=1; i<=n; ++i)
{
if (a[i].x<s.x || a[i].x>t.x) continue;
a[++top]=a[i];
}
a[++top]=t;
n=top;
double ans=0;
head[0]=tail[0]=head[1]=tail[1]=1;
q[0][1]=q[1][1]=&s;
for (int i=1; i<=n; ++i)
{
int x=a[i].direction, y=x^1;
if (head[y]<tail[y] && ( (a[i]-*q[y][head[y]])*(*q[y][head[y]+1]-*q[y][head[y]]) )*(x==1 ? 1 : -1)>=0)
{
while (head[y]<tail[y] && ( (a[i]-*q[y][head[y]])*(*q[y][head[y]+1]-*q[y][head[y]]) )*(x==1 ? 1 : -1)>=0) ++head[y];
a[i].Next=q[y][head[y]];
head[x]=tail[x]=tail[x]+1;
q[x][head[x]]=q[y][head[y]];
}
else
{
while (head[x]<tail[x] && ( (a[i]-*q[x][tail[x]-1])*(*q[x][tail[x]]-*q[x][tail[x]-1]) )*(x==1 ? 1 : -1)>=0) --tail[x];
a[i].Next=q[x][tail[x]];
}
q[x][++tail[x]]=&a[i];
}
for (Orz *now=&a[n], *last; *now!=s;)//*now 指 a[n] 的值,而 now 指 a[n] 的地址
{
last=now; now=now->Next;//访问 Next
ans+=Dis(*now-*last);
}
printf("%.10f\n",ans);
return 0;
}