题干
E - Laser Takahashi


AC代码概览
cpp
#define _CRT_SECURE_NO_WARNINGS 1
/*题目描述
https://atcoder.jp/contests/abc442/tasks/abc442_e
*/
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int maxn = 2e5 + 5;
struct Node {
int x, y, id, p;
void cal_p() {
if (!x)
p = (y > 0) ? 0 : 4;
else if (!y)
p = (x > 0) ? 2 : 6;
else if (x > 0 && y > 0)
p = 1;
else if (x > 0 && y < 0)
p = 3;
else if (x < 0 && y < 0)
p = 5;
else
p = 7;
}
} a[maxn];
int n, q, pos[maxn];
int pre[maxn], nxt[maxn];
bool cmp(Node a, Node b) {
if (a.p != b.p)
return a.p < b.p;
int v1 = abs(a.y) * abs(b.x);
int v2 = abs(b.y) * abs(a.x);
if (a.p == 1 || a.p == 5)
return v1 > v2;
return v1 < v2;
}
int32_t main() {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
cin >> n >> q;
for (int i = 1; i <= n; i++) {
cin >> a[i].x >> a[i].y;
a[i].id = i;
a[i].cal_p();
}
sort(a + 1, a + n + 1, cmp);
for (int i = 1; i <= n; i++) {
pos[a[i].id] = i;
if (i > 1 && a[i].p == a[i - 1].p
&& a[i].y * a[i - 1].x == a[i - 1].y * a[i].x)
pre[i] = pre[i - 1];
else
pre[i] = i;
}
for (int i = n; i >= 1; i--) {
if (i < n && a[i].p == a[i + 1].p
&& a[i].y * a[i + 1].x == a[i + 1].y * a[i].x)
nxt[i] = nxt[i + 1];
else
nxt[i] = i;
}
for (int i = 0, a, b, ans; i < q; i++) {
cin >> a >> b;
a = pos[a];
b = pos[b];
if (pre[a] == pre[b] || a < b)
ans = nxt[b] - pre[a] + 1;
else
ans = nxt[b] + n - pre[a] + 1;
cout << ans << endl;
}
return 0;
}
逐段解析一
cpp
struct Node {
int x, y, id, p;
void cal_p() {
if (!x)
p = (y > 0) ? 0 : 4;
else if (!y)
p = (x > 0) ? 2 : 6;
else if (x > 0 && y > 0)
p = 1;
else if (x > 0 && y < 0)
p = 3;
else if (x < 0 && y < 0)
p = 5;
else
p = 7;
}
} a[maxn];
我们将一个坐标系划分成8个部分其中0,2,4,6是坐标轴,1,3,5,7是四象限
cpp
bool cmp(Node a, Node b) {
if (a.p != b.p)
return a.p < b.p;
int v1 = abs(a.y) * abs(b.x);
int v2 = abs(b.y) * abs(a.x);
if (a.p == 1 || a.p == 5)
return v1 > v2;
return v1 < v2;
}
...
sort(a + 1, a + n + 1, cmp);
这里的比较函数首先通过八个部分进行比较,接着在相同部分的情况下,通过斜率进行比较,之所以不用角度或者弧度进行比较是因为后者涉及到小数比较不够精准也不够方便,但是同过斜率的不等式我们很容易得到"乘式",也就是说只需要进行整数之间的比较。这样做的本质原因是题目中要求顺时针,再加上数据量足够我们进行nlog(n)的遍历,所以我们有必要先对所有坐标进行一个顺时针的排序
逐段解析二
cpp
for (int i = 1; i <= n; i++) {
pos[a[i].id] = i;
if (i > 1 && a[i].p == a[i - 1].p
&& a[i].y * a[i - 1].x == a[i - 1].y * a[i].x)
pre[i] = pre[i - 1];
else
pre[i] = i;
}
for (int i = n; i >= 1; i--) {
if (i < n && a[i].p == a[i + 1].p
&& a[i].y * a[i + 1].x == a[i + 1].y * a[i].x)
nxt[i] = nxt[i + 1];
else
nxt[i] = i;
}
1, pos[a[i].id] = i;
这一段是为了给排好序的a[i]中的每个坐标给他们标好rank,至此排序结束。
2,其余部分是 为了解决题目中所提到的可能出现在同一射线上的点,也就是在进行区间统计时如果不进行适当的操作会造成边界部分损失的问题,本质上是没有定义适当的边界,那么我们通过向前遍历所有的点可以找出在同一条射线上的点并将他们分组,这个组是前边沿组,也就是这个组的值是最前边沿的点的编号,同理通过向前遍历所有的点我们可以的到末边沿的组,这个组的值是最末边沿的点的编号。这样以来我们就可以确定最适当的边沿(最大区间,避免损失)。
逐段解析三
cpp
for (int i = 0, a, b, ans; i < q; i++) {
cin >> a >> b;
a = pos[a];
b = pos[b];
if (pre[a] == pre[b] || a < b)
ans = nxt[b] - pre[a] + 1;
else
ans = nxt[b] + n - pre[a] + 1;
cout << ans << endl;
}
最后一部分也就是输出部分,根据每个查询给出的两个坐标编号,我们先确定他们在顺时针中的实际顺序:
cin >> a >> b;
a = pos[a];
b = pos[b];
接下来我们需要确定他们的位置关系:
1,在一条射线上
2,左小右大
3,右小左大
其中1,2情况处理方法一致,只需要大减小得出中间右多少个
第三个情况需要先用2的方法做再加个n(可以看成用n减去情况三)
