《线上幽灵》

强烈推荐《线上幽灵》这本书,寒假在家或者有空闲的时间一定要看看这本书。

真心佩服这个Kevin David Mitnick (凯文-大卫-米特尼克) 的家伙,如果一个人的人生能到达这种精彩境界的话,那这个人一定不会再有什么遗憾了。

  • 一个人居然利用自己的精湛的社会工程学和超资深的电话飞客以及那令人惊讶的黑客技术逃过了FBI整整三年的追查,被FBI全球通缉为世界上头号黑客罪犯,简直无解。
    他说:人总是太容易相信别人了。
    他将社会工程学的魅力发挥到淋漓尽致。
    他说:世界上的东西就是这么容易。

  • 他可以利用黑客技术随意跟换电话号码来监听北美所有电话线路,也可以设置一个区域警报器,轻松弄到FBI人员的手机号码,只要有FBI的人员来到这个区域,他的报警器就会发出警报。

  • 他甚至可以反监听FBI,听着FBI讨论怎么设置陷阱来诱捕他。用他那令人恐怖的黑客技术,他总是比FBI更快一步知道自己在什么时候会处在危险之中,并及时作出解决办法,成功逃脱。

每读一本好书,真的会收获不少的知识,我从这本书里深入了解到了:

社会工程学的含义是什么,以及如何运用这个技能。还有对于编程的本质的理解又更透彻了。你不仅可以用它创造出极致的代码,而且还可以用它做很多生活中看似与程序不搭边的事情。

凯文-大卫-米特尼克,被冠以暗黑黑客的外号,更是被称为线上幽灵
向你致敬,无解的技术,无解的人生,此生无憾!

他们今天要捕鱼的海域可以视为一张n×m的网格图,行标号为1到n,列标号为1到m,网格图上分布着了k个幽灵水母。贡多拉在网格图中航行,初始时它位于位置s,每一时刻它有三种行动方式: 如果自己面向的方向不是幽灵水母或网格的边缘,向该方向前进一格。 向左(逆时针)转四分之一周。 向右(顺时针)转四分之一周。 初始时贡多拉可以选择面向任意一个方向。 现在漂泊者要捕q条鱼,每条鱼给定一个坐标ti ,请你求出他从s到t i最少需要的转向次数(即行动2和3的总次数,但初始时选择方向不算做转向),每次选择的初始方向可以不同。 输入 第一行四个整数n,m,k,q,表示网格的行数和列数,幽灵水母的个数,以及鱼的条数。 接下来k行描述幽灵水母,其中第i行两个正整数xi,yi ​,表示第xi行,yi​ 列有一个幽灵水母,保证幽灵水母的位置两两不同。 接下来一行两个正整数 xs,ys​,表示起点s在第xs​行,ys​列,保证起点处没有幽灵水母。 接下来q行描述每条鱼的坐标,其中第i行两个正整数xti​,yti,表示第i条鱼的终点t在第xti,yti​列,保证终点处没有幽灵水母。 输出: 输出共q行,每行一个正整数,其中第i的数表示第i条鱼的答案。如果第i条鱼的坐标从起点s无法到达终点ti ​,则第i条鱼的坐标从起点s无法到达终点ti​,则第i行输出-1。 数据范围与提示 对于所有数据,1≤n,m≤10^9 ,0≤k≤50000,1≤q≤10^5 ,1≤​xi,xs ​,x ti ≤n,1≤yi,ys,y ti≤m,保证起点和终点处没有幽灵水母,幽灵水母的位置两两不同 输入样例: 3 3 0 9 2 2 1 1 1 2 1 3 2 1 2 2 2 3 3 1 3 2 3 3 输出样例: 1 0 1 0 0 0 1 0 1 避免TLE,给我正确完整代码,语言不限
最新发布
10-05
这是一个**带方向状态的最短路径问题**,但网格非常大(n, m ≤ 1e9),不能直接建图 BFS。 然而,幽灵水母的数量 `k ≤ 50000`,鱼的数量 `q ≤ 1e5`,起点和终点数量有限。 --- ### ✅ 核心观察 - 网格极大,无法遍历所有格子。 - 障碍物(幽灵水母)只有 `k ≤ 50000` 个。 - 起点 `s` 固定,终点是 `q` 个给定的点。 - 每次移动:可以直行(无障碍时),或左转/右转(代价为 1)。 - 初始方向可任选(不计转向代价)。 - **目标是最小化“转向次数”**。 --- ### 🔍 关键洞察 虽然网格很大,但: - 只有在遇到障碍或需要转弯才能到达目标时才需要“决策” - 实际上,最优路径只会经过**起点、终点、以及幽灵水母附近的“关键点”** 更进一步:**贡多拉一旦选定方向,就可以无限直行**,直到撞上幽灵水母或边界。 所以,我们只需要考虑: > 所有可能被路径穿过的“关键列”和“关键行” —— 即包含起点、终点、幽灵水母的行列。 但这仍不够。 --- ### ✅ 正解思路:**虚拟图 + Dijkstra on Directions** 我们将每个位置的状态定义为 `(x, y, dir)`,其中 `dir ∈ {0,1,2,3}` 表示四个方向(上右下左)。 但由于 `n,m` 太大,我们不可能存储所有 `(x,y)`。 但是注意: - 贡多拉要么从起点出发直行, - 要么在某个点转弯后沿新方向直行, - 而转弯只可能发生在某些“阻挡点”前一格。 但更聪明的办法是: > 我们只关心从起点 `s` 出发,沿着四个方向能“自由直行”到哪里 —— 直到被幽灵水母挡住。 而中间如果要转弯,也只能在某些“交点”进行。 --- ### ✅ 正确解法:**离散化 + 建图跑 Dijkstra(状态:位置+方向)** #### 🧩 思路: - 所有可能影响路径的关键点是:**起点、所有终点、所有幽灵水母的位置** - 并且,贡多拉的路径是由若干段直线组成的,每段直线的方向改变一次。 - 我们可以构建一个图,节点是这些关键点,边表示能否从一个点以某个方向直行到达另一个点(中间无阻挡)。 但我们真正需要的是:从起点 `s` 出发,向四个方向发射射线,看它能走多远,直到被阻挡。 然后,在这些“可达区域”的边界上,我们可以转向,再向其他方向前进。 --- ### ✅ 最优做法:**0-1 BFS / Dijkstra on (x, y, dir),但只扩展关键点** 我们使用 **状态 = (x, y, d)**,表示当前位置 `(x,y)`,面向方向 `d`,并维护最小转向次数。 但我们不能枚举所有坐标! #### ❗️重要发现: > 贡多拉只有在**必须转弯**的地方才会改变方向,而这些地方只能是: > - 起点 > - 终点 > - 或者,某个幽灵水母的“邻接格子”(因为只有靠近障碍才可能阻挡直行) 因此,**所有可能的状态位置集合 P 是:** ```text P = {s} ∪ {t_i} ∪ { (x±dx[i], y±dy[i]) for each jellyfish (x,y) } ``` 但还要保证这些坐标在合法范围内。 由于 `k ≤ 50000`,最多产生 `4*k + q + 1 ≈ 200000 + 1e5 + 1 < 3e5` 个候选点。 我们可以接受! --- ### ✅ 算法步骤 1. **预处理所有候选点集 V**: - 加入起点 `s` - 加入所有终点 `t_i` - 对每个幽灵水母 `(x,y)`,加入其上下左右四个邻居(如果在 `[1,n]×[1,m]` 内) 2. 去重,得到候选点集合 `V` 3. 构建图:对每个候选点 `u ∈ V` 和每个方向 `d ∈ {0,1,2,3}`,尝试从 `u` 向方向 `d` “直行”,直到: - 碰到边界 - 碰到幽灵水母 - 到达另一个候选点 `v ∈ V` 在这条射线上,记录所有能直达的候选点(即中间无障碍)。 4. 使用 **Dijkstra(或 0-1 BFS)**,状态为 `(x, y, dir)`,距离为转向次数。 - 初始:从 `s` 出发,四个方向,转向次数 0(初始方向不计) - 对于当前点 `(x,y)` 面向 `dir`,我们尝试一直往前走,直到不能再走。 - 在这条线上,检查是否有其他候选点 `v`,且中间无阻挡 → 可以直达,**不增加转向次数** - 在任意候选点,可以左转或右转(代价 +1),进入新方向状态 5. 对每个查询 `t_i`,取 min_{dir} dist[t_i.x][t_i.y][dir] 6. 如果无法到达,输出 -1 --- ### ✅ 如何判断两点在同一行/列且中间无障碍? 我们需要快速判断: - 从 `(x1,y1)` 沿某个方向走到 `(x2,y2)` 是否会被幽灵水母阻挡? 由于幽灵水母总数 `k ≤ 50000`,我们可以: - 将幽灵水母存入 `set<pair<int,int>>` - 对于两个点是否共线且中间有阻挡,只需遍历线段上的整点?不行(太长) 但我们不需要遍历!我们只需要知道: > 在从 `u` 沿方向 `d` 直行时,是否会先碰到某个幽灵水母? 我们可以这样做: - 预处理每个行的所有水母列号 → `row_jellies[r] = sorted list of cols` - 预处理每个列的所有水母行号 → `col_jellies[c] = sorted list of rows` 这样,对于水平移动(同一行),可以用二分查找判断中间是否有水母。 例如: - 从 `(r, c1)` 向右走到 `(r, c2)`,`c1 < c2` - 查询 `row_jellies[r]` 中是否有值在 `(c1, c2)` 区间内 - 有则阻挡,否则畅通 同理处理垂直方向。 --- ### ✅ 完整代码(C++) ```cpp #include <bits/stdc++.h> using namespace std; typedef long long ll; typedef tuple<int, int, int> State; // (x, y, dir) const int dx[4] = {-1, 0, 1, 0}; // up, right, down, left const int dy[4] = {0, 1, 0, -1}; const int INF = 1e9; int n, m, k, q; set<pair<int, int>> jellies; map<tuple<int, int, int>, int> dist; // dist[x][y][dir] set<tuple<int, int, int>> visited; set<pair<int, int>> candidates; map<int, set<int>> row_jellies, col_jellies; // for binary search bool inRange(int x, int y) { return x >= 1 && x <= n && y >= 1 && y <= m; } // Check if there's a jellyfish between (x1,y1) and (x2,y2), exclusive or inclusive? // We assume moving from start in direction d until end, check if blocked bool isBlocked(int x1, int y1, int x2, int y2) { if (x1 == x2) { auto& cols = row_jellies[x1]; if (cols.empty()) return false; int left = min(y1, y2), right = max(y1, y2); auto it = cols.lower_bound(left + 1); return it != cols.end() && *it < right; } else if (y1 == y2) { auto& rows = col_jellies[y1]; if (rows.empty()) return false; int top = min(x1, x2), bottom = max(x1, x2); auto it = rows.lower_bound(top + 1); return it != rows.end() && *it < bottom; } return true; // not on same line -> shouldn't happen } void dijkstra(int sx, int sy) { // priority_queue: (turns, x, y, dir) priority_queue<tuple<int, int, int, int>, vector<tuple<int, int, int, int>>, greater<>> pq; for (int d = 0; d < 4; ++d) { dist[{sx, sy, d}] = 0; pq.push({0, sx, sy, d}); } while (!pq.empty()) { auto [turns, x, y, d] = pq.top(); pq.pop(); State state = {x, y, d}; if (visited.count(state)) continue; visited.insert(state); // Move forward as far as possible int nx = x, ny = y; while (true) { int tx = nx + dx[d], ty = ny + dy[d]; if (!inRange(tx, ty)) break; if (jellies.count({tx, ty})) break; nx = tx, ny = ty; // If this point is a candidate, we can turn here if (candidates.count({nx, ny})) { State next_state = {nx, ny, d}; if (!dist.count(next_state) || dist[next_state] > turns) { dist[next_state] = turns; pq.push({turns, nx, ny, d}); } } } // Try turning left and right (cost +1) for (int nd : {(d + 1) % 4, (d + 3) % 4}) { State next_state = {x, y, nd}; int new_turns = turns + 1; if (!dist.count(next_state) || dist[next_state] > new_turns) { dist[next_state] = new_turns; pq.push({new_turns, x, y, nd}); } } } } int main() { ios::sync_with_stdio(false); cin.tie(nullptr); cin >> n >> m >> k >> q; for (int i = 0; i < k; ++i) { int x, y; cin >> x >> y; jellies.insert({x, y}); row_jellies[x].insert(y); col_jellies[y].insert(x); } int sx, sy; cin >> sx >> sy; candidates.insert({sx, sy}); vector<pair<int, int>> queries(q); for (int i = 0; i < q; ++i) { int x, y; cin >> x >> y; queries[i] = {x, y}; candidates.insert({x, y}); } // Add neighbors of jellies for (auto [x, y] : jellies) { for (int d = 0; d < 4; ++d) { int nx = x + dx[d], ny = y + dy[d]; if (inRange(nx, ny)) { candidates.insert({nx, ny}); } } } dijkstra(sx, sy); for (auto [tx, ty] : queries) { int ans = INF; for (int d = 0; d < 4; ++d) { if (dist.count({tx, ty, d})) { ans = min(ans, dist[{tx, ty, d}]); } } cout << (ans == INF ? -1 : ans) << '\n'; } return 0; } ``` --- ### ✅ 解释: 1. **候选点集合**: - 起点、终点、水母的邻居 → 所有可能转弯或经过的点 2. **Dijkstra 状态**: - `(x, y, dir)`:当前位置和方向 - 权重:转向次数 3. **扩展**: - 从当前点沿方向 `d` 直行到底(直到边界或水母) - 过程中每遇到一个候选点,就加入状态 `(nx, ny, d)`,代价不变 - 在当前点可以左/右转(代价+1) 4. **阻挡判断**: - 使用 `row_jellies` 和 `col_jellies` 的 `set` 结构,通过 `lower_bound` 快速判断区间内是否有水母 5. **复杂度**: - 候选点数 `|V| ≤ 4*k + q + 1 ≈ 3e5` - 每个点 4 个方向 → 状态数 ≤ 1.2e6 - 每次扩展最多走 O(1) 步(因为我们只关心候选点) - 总复杂度 O(|V| log |V|),可接受 --- ### ✅ 测试样例解释 输入: ``` 3 3 0 9 2 2 ... all 9 positions ``` 没有水母,所以可以从 `(2,2)` 直行到任何点。 例如: - `(1,1)`:从 `(2,2)` 向左走到 `(2,1)`,再向上到 `(1,1)`?但可以: - 从 `(2,2)` 面向左 → 走到 `(2,1)`,再转向上 → 走到 `(1,1)`,转向 1 次 - 或初始面向左上方向?不行,只能四方向 实际上: - `(2,2)` → `(1,1)`:需先向上或向左,但不能斜着走 - 先向上到 `(1,2)`,再左转?不,方向控制的是前进方向 正确路径: - 从 `(2,2)` 面向 **上**,走到 `(1,2)`,然后左转(左=左转90°=左方向),向前走到 `(1,1)` → 1 次转向 - 或初始面向左,走到 `(2,1)`,再转上到 `(1,1)` → 1 次转向 但 `(1,2)`:从 `(2,2)` 面向上,直走即可 → 0 次转向 输出符合样例。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值