P11193 [COTS 2021] 虫 Kukac 题解
逛题目列表时发现的题目,分享后被 @H3PO4 秒了,特此膜拜(%%%)。
题目大意
平面上有一个
三个点
前置知识:射线法
简单地说,判断一点是否在多边形内,可以从该点向任意方向引出一条射线,若射线与多边形有奇数个交点,那么该点在多边形内,否则在多边形外,如图:
图1中点
简易证明:
首先得知道,一个多边形只会把平面分成两部分,一部分是在多边形内的,另一部分是在多边形外的,因此一个点只有在内、在外两种状态(这里不讨论在多边形上的情况)。
从一点出发沿着射线方向走,每穿过一次多边形,都会改变自己的状态(也就是从内部到外部,或从外部到内部),而沿着射线方向走总会走到多边形外部,因此只需要知道穿过次数的奇偶性,就可以逆推回去。
但是这样说是有缺陷的,如图:
当点
如此地,我们就可以证明上述方法的正确性。
回到本题
知道了上述结论,我们就需要从点
那么就来到了第二个问题,如何判断一条线段是否与射线相交呢?
图中的点是顺序给出的,因此我们可以知道如果一条线段与射线相交,它们两端的端点一定在异侧(因为题目无三点共线)。这样我们就可以询问
然后就可以得出每个线段与直线
但是这样会产生两个问题,首先便是我们判断的是线段与直线的关系,而我们用的是射线法。其次,这样也并不能直接根据相交线段的数量而判断,而是要看判断改变状态的次数,这在上方证明时就已提过。
但是,第二个问题是好解决的,如果射线途中不经过多边形的任何端点,那么答案就等于与射线相交的线段数量。而题目保证了无三点共线,因此我们考虑换一下,以
为了方便,设此时射线为
我们钦定
因为你不知道这个左方向到底是什么,所以你需要询问直线
至此我们就解决了这个问题的
参考代码
#include <bits/stdc++.h>
#define ll long long
const int N = 500 + 10;
using namespace std;
int read () {
int x = 0, k = 1; char c = getchar();
while (c < '0' || c > '9') { if (c == '-') k = -1; c = getchar(); }
while (c >= '0' && c <= '9') x = (x << 3) + (x << 1) + (c ^ 48), c = getchar();
return (1ll * x * k);
}
int n, q;
int dire[N], lft, cnt;
int query (int A1, int A2, int p) {
return printf("? %d %d %d\n", A1, A2, p), cout.flush(), read();
}
signed main() {
n = read(), q = read();
// 询问每个点与直线 OP1 的关系(上下)
for (int i = 2; i <= n; ++i) dire[i] = query(0, 1, i);
// 钦定 P1P2 与 O 询问的返回值为左方向,注意要从下往上
lft = dire[2] ? query(1, 2, 0) : query(2, 1, 0);
for (int i = 3; i <= n; ++i)
if (dire[i] != dire[i - 1]) // 判断是否与直线相交
if ((dire[i] ? query(i, i - 1, 0) : query(i - 1, i, 0)) == lft) // 判断是否与射线相交
++cnt;
printf("! %d\n", cnt & 1);
return 0;
}
时间复杂度:
参考文献
- 判断一点是否在任意多边形内部
- 判断点是否在多边形内(射线法)
- @H3PO4 的神秘口糊(%%%)