CF274E Mirror Room题解

传送门(洛谷)

传送门(codeforces)

题意

\(n \times m\) 的平面,有 \(k\) 个障碍物,激光碰到障碍物后会反射,给定激光初始位置和方向,求激光能经过多少障碍物。

解法

首先需要证明两个性质:

  1. 激光反弹次数为 \(O(n+m+k)\) 级别。
  • 证明:对角线级别为 \(O(n+m)\),有 \(k\) 个障碍物把这些对角线分为 \(O(k)\) 份,总数为 \(O(n+m+k)\)
  1. 同一个方格不会经过交叉激光。
  • 证明:类似黑白染色,把相邻的染成不同颜色,然后发现,同一方向上的路径所经过的放格颜色总相同,因为每次转向一定会变换到相邻的颜色不同的格子。

根据第一个性质,可以想到快速找到下一次碰到障碍的位置,可以利用 set 存对角线上的点,每次 \(ans\) 累加移动距离,由于性质二,不会有交叉算重的情况,如果反向了怎么办?

处理方法:从当前点能够到达的某个边界(或障碍物附近)开始运行代码,过程中如果出现走反向边的情况标记一下,容易证明,一旦走到反向边,激光会按照原路返回,所以每个点会被恰好计算两次,除以二即可,如果没有经过反向,则每个点只会经过一次(因为既没有交叉,又没有反向),所以,每个点只会经过一次。

一些细节:边界也要算作障碍。

代码

// Problem: E. Mirror Room
// Contest: Codeforces - Codeforces Round #168 (Div. 1)
// URL: https://codeforces.com/problemset/problem/274/E
// Memory Limit: 256 MB
// Time Limit: 2000 ms
//
// Powered by CP Editor (https://cpeditor.org)

#include <bits/stdc++.h>
#define int long long
using namespace std;
namespace Std {
int n, m, k, x, y, d, tx, ty, td, cs = 1, ans;
set<pair<int, int> > cset[2][200010];
unordered_map<int, int> cmap[100010];
const int dx[] = {1, -1, -1, 1}, dy[] = {-1, -1, 1, 1};
char s[5];
inline void add(int a, int b) {
  cmap[a][b] = 1;
  cset[0][a + b].insert(make_pair(a, b));
  cset[1][b - a + n].insert(make_pair(a, b));
}
void work(bool opt) {
  auto it = cset[d & 1][d & 1 ? y - x + n : x + y].lower_bound(make_pair(x, y));
  if (d == 1 || d == 2) it--;
  if (opt) ans += abs(x - (*it).first);
  x = (*it).first - dx[d], y = (*it).second - dy[d];
  int cnt = cmap[x + dx[d]].count(y) + cmap[x].count(y + dy[d]);
  if ((!cnt) || cnt == 2) {
    cs = 2;
    d ^= 2;
  } else if (cmap[x + dx[d]].count(y)) {
    y += dy[d];
    d ^= 1;
  } else {
    x += dx[d];
    d ^= 3;
  }
}
int main() {
  scanf("%lld%lld%lld", &n, &m, &k);
  int a, b;
  for (int i = 1; i <= k; ++i) {
    scanf("%lld%lld", &a, &b);
    add(a, b);
  }
  for (int i = 1; i <= n; ++i) {
    add(i, 0);
    add(i, m + 1);
  }
  for (int i = 1; i <= m; ++i) {
    add(0, i);
    add(n + 1, i);
  }
  scanf("%lld%lld", &x, &y);
  scanf("%s", s + 1);
  if (s[1] == 'S') d += 2;
  if ((s[1] == 'S' && s[2] == 'E') || (s[1] == 'N' && s[2] == 'W')) ++d;
  add(0, 0);
  add(0, m + 1);
  add(n + 1, 0);
  add(n + 1, m + 1);
  work(false);
  tx = x;
  ty = y;
  td = d;
  work(true);
  while (x != tx || y != ty || d != td) {
    work(true);
  }
  printf("%lld\n", ans / cs);
  return 0;
}
}  // namespace Std
#undef int
int main() { return Std::main(); }
posted @ 2022-05-09 21:02  Wilson_Inversion  阅读(28)  评论(0编辑  收藏  举报