题目链接:https://acm.hdu.edu.cn/showproblem.php?pid=4208

题意重述

平面上有 n 条线段镜子(1 <= n <= 7),激光从原点发射。
激光遇到镜子按反射定律反射。找出所有能在 不超过 7 次反射 后回到原点的初始角度(0~359,四舍五入到最近整数,去重升序)。

没有则输出 no danger

思路

1. 展开法生成候选方向

若路径依次打到镜子序列 m1..mk1<=k<=7),把原点按该序列逐次做镜像反射得到像点 P,则从原点朝 P 发射是候选方向。

枚举所有长度 1..7 的镜子序列(最多 7^7),得到一批候选方向。

2. 几何模拟做真值校验

候选方向不一定真能走出合法反射路径,所以每个候选都做精确模拟:

  • 维护当前位置 p、方向 d
  • 求射线与所有镜段最近交点
  • 同时判断该射线是否先回到原点
  • 若先回原点且反射次数在 1..7,该角度有效
  • 否则在最近镜面反射继续,最多 7 次

3. 数值稳定性

这题最容易 WA 在浮点误差。实现上用:

  • long double
  • 相交/共线判断用“相对误差”
  • tOrigin + eps < tMirror 再判“先回原点”

C++17 代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
#include <bits/stdc++.h>
using namespace std;

using ld = long double;

static const ld EPS = 1e-12L;
static const ld INF = 1e100L;
static const ld PI = acosl(-1.0L);

struct Pt {
ld x, y;
Pt() : x(0), y(0) {}
Pt(ld x_, ld y_) : x(x_), y(y_) {}
Pt operator + (const Pt& o) const { return Pt(x + o.x, y + o.y); }
Pt operator - (const Pt& o) const { return Pt(x - o.x, y - o.y); }
Pt operator * (ld k) const { return Pt(x * k, y * k); }
};

struct Seg {
Pt a, b;
};

ld dot(const Pt& a, const Pt& b) { return a.x * b.x + a.y * b.y; }
ld cross(const Pt& a, const Pt& b) { return a.x * b.y - a.y * b.x; }
ld norm2(const Pt& a) { return dot(a, a); }

bool isParallel(const Pt& a, const Pt& b) {
ld den = fabsl(cross(a, b));
ld scale = sqrtl(max((ld)1.0, norm2(a) * norm2(b)));
return den <= EPS * scale;
}

bool isCollinear(const Pt& a, const Pt& b) {
ld den = fabsl(cross(a, b));
ld scale = sqrtl(max((ld)1.0, norm2(a) * norm2(b)));
return den <= EPS * scale;
}

Pt reflectPoint(const Pt& p, const Seg& s) {
Pt e = s.b - s.a;
ld t = dot(p - s.a, e) / dot(e, e);
Pt h = s.a + e * t;
return h * 2.0L - p;
}

Pt reflectDir(const Pt& d, const Seg& s) {
Pt e = s.b - s.a;
ld k = 2.0L * dot(d, e) / dot(e, e);
return e * k - d;
}

bool raySegIntersectT(const Pt& p, const Pt& d, const Seg& s, ld& tOut) {
Pt e = s.b - s.a;
if (isParallel(d, e)) return false;

Pt ap = s.a - p;
ld den = cross(d, e);
ld t = cross(ap, e) / den;
ld u = cross(ap, d) / den;

if (t <= EPS) return false;
if (u <= EPS || u >= 1.0L - EPS) return false;

tOut = t;
return true;
}

bool rayHitOriginT(const Pt& p, const Pt& d, ld& tOut) {
Pt v(-p.x, -p.y);
if (!isCollinear(v, d)) return false;
ld t = dot(v, d) / norm2(d);
if (t <= EPS) return false;
tOut = t;
return true;
}

int angleRound(const Pt& d) {
ld deg = atan2l(d.y, d.x) * 180.0L / PI;
if (deg < 0) deg += 360.0L;
int a = (int)floorl(deg + 0.5L);
a %= 360;
return a;
}

bool simulate(const Pt& initDir, const vector<Seg>& mirrors) {
Pt p(0.0L, 0.0L), d = initDir;
int refl = 0;

while (true) {
ld tOrigin = INF, tTmp;
if (rayHitOriginT(p, d, tTmp)) tOrigin = tTmp;

ld bestT = INF;
int bestIdx = -1;
for (int i = 0; i < (int)mirrors.size(); ++i) {
if (raySegIntersectT(p, d, mirrors[i], tTmp) && tTmp < bestT) {
bestT = tTmp;
bestIdx = i;
}
}

if (tOrigin + EPS < bestT) {
return (refl >= 1 && refl <= 7);
}

if (refl == 7) return false;
if (bestIdx == -1) return false;

p = p + d * bestT;
d = reflectDir(d, mirrors[bestIdx]);
++refl;
}
}

void dfs(int depth, int limit, Pt imagePoint,
const vector<Seg>& mirrors, set<int>& ans) {
if (depth == limit) {
if (fabsl(imagePoint.x) <= EPS && fabsl(imagePoint.y) <= EPS) return;
if (simulate(imagePoint, mirrors)) {
ans.insert(angleRound(imagePoint));
}
return;
}

for (int i = 0; i < (int)mirrors.size(); ++i) {
Pt nxt = reflectPoint(imagePoint, mirrors[i]);
dfs(depth + 1, limit, nxt, mirrors, ans);
}
}

int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);

int n;
while (cin >> n) {
if (n == 0) break;

vector<Seg> mirrors(n);
for (int i = 0; i < n; ++i) {
cin >> mirrors[i].a.x >> mirrors[i].a.y
>> mirrors[i].b.x >> mirrors[i].b.y;
}

set<int> ans;
Pt O(0.0L, 0.0L);

for (int len = 1; len <= 7; ++len) {
dfs(0, len, O, mirrors, ans);
}

if (ans.empty()) {
cout << "no danger\n";
} else {
bool first = true;
for (int a : ans) {
if (!first) cout << ' ';
cout << a;
first = false;
}
cout << '\n';
}
}

return 0;
}

样例说明

样例输出为:

  • 45 67 90 135 157 180
  • 29 61 63 117
  • no danger

易错点

  1. 只做展开不做模拟会错。
  2. 要比较“先撞镜子”还是“先回原点”。
  3. 浮点误差会导致误判,建议 long double + 相对 eps