说在前面,其他博客中的代码应该保证不了健壮性,我这个(应该)可以!
题目
题解
数学:计算几何。
提示:
这题默认好像是顺时针或逆时针输入坐标,也就是说先后输入的两个点一定是多边形的一条边。
前置知识:PNPoly算法
何为PNPoly算法算法?
通俗的来讲,这个算法用于判断一个点是否位于一个封闭的多边形中。
判断方式是以这个点为始端向一个选定的方向作射线,若射线穿过奇数条多边形的边,则说明点位于多边形内,为偶数则说明不在多边形内。
原理很简单,若这个点位于多边形内部,那么这条射线第一次穿过多边形的一条边必然是到达了多边形的外面,若又穿过了一条边,则必然是又回到了多边形内部,……,如此下去,存在交替的规律;同样的,若点处于多边形外面,则射线第一次穿过多边形的一条边一定是进入到多边形的内部了,再穿过一条就出多边形了,……。
如下图的蓝色和红色的射线均穿过奇数条边,因此属于多边形内部;而紫色的穿过偶数条边,属于多边形外部。
伪代码:
bool check(要判断的点的横坐标x, 要判断的点的纵坐标y) {
bool flag = false;
for(多边形上相邻的点:i和j) {
x1=i的横坐标, x2=j的横坐标, y1=i的纵坐标, y2=j的纵坐标
if(x在y1、y2的范围内) {
tmp_x = 将y代入到(x1, y2)、(x2, y2)构成的直线的两点式中,求出对应的tmp_x
if(tmp_x 位于 x 左侧,即x>tmp_x) flag = !flag;
}
}
return false;
}
知道了这个算法本题就相当于解决了一大半了。
整体思路:
我们确定出输入的点中x坐标的上界和下界,y坐标的上界和下界,满足要求的格子一定是在这个坐标范围内的,因此我们对这个范围内的格子的四个点执行PNPoly算法进行判断即可,若四个点都处于多边形内部,则说明这个格子也处于多边形内部,此时记录个数的ans++。
此时你可以先理解一下下面的“非完美AC代码”,有助于你理解下面如何修改成完美代码。
看起来这样就结束了,确实结束了,可以AC,但是很多数据是过不了的,举个简单的例子:
4
0 0
0 1
1 1
1 0
对应的图像为:
很显然答案是1,但是很多博主或者AC代码的解都为0,具体原因可以模拟分析一遍。
我为了解决健壮性的问题,再三考虑后,终于想出解决方法了。
在向check中传入参数时,我们不再传格子四个点的坐标,而是取稍小于格子一点的四个坐标,如下图为1×1的方格:
我们不再以红色区域的四个点的坐标作为方格的四个角的坐标了,而是以绿色虚线区域的四个角的坐标作为方格四个角的坐标。
这个绿色区域的大小其实与红色区域的大小所差无几,这只是为了解决对于有些方案无法输出正确答案的问题。
因此在这里我设置了一个det常量,表示绿色边框与红色边框的距离,每次向check中传入的是绿框的四个角的位置。
举几个比较奇葩的例子:
例子1
8
0 0
0 3
3 3
3 0
2 0
2 2
1 2
1 0
7
例子2
12
0 2
1 4
2 4
2 3
1 3
1 2
3 2
3 3
4 3
4 2
3 1
4 0
3
例子1例子2对应的图像分别是:
非完美AC代码
#include<bits/stdc++.h>
using namespace std;
const int N = 1010;
const double det = 0.00001;
struct node {
int x, y;
} po[N];
int upx, lox, upy, loy, n, ans;
int check(int x, int y) {
bool flag = false;
for(int i = 1, j = n;i <= n;j = i++) {
double x1 = po[i].x, y1 = po[i].y, x2 = po[j].x, y2 = po[j].y;
if(min(y1, y2) < y && y <= max(y1, y2)) {
double tx = (x2-x1)*(y-y1)/(y2-y1)+x1;
if(x > tx) flag = !flag;
}
}
return flag;
}
int main()
{
cin>>n;
for(int i = 1;i <= n;i ++) {
cin>>po[i].x>>po[i].y;
upx = max(upx, po[i].x);
lox = min(lox, po[i].x);
upy = max(upy, po[i].y);
loy = min(loy, po[i].y);
}
for(int i = lox;i < upx;i ++)
for(int j = loy;j < upy;j ++) {
if( check(i, j) &&
check(i+1, j) &&
check(i, j+1) &&
check(i+1, j+1))
ans ++;
}
cout << ans << endl;
return 0;
}
完美AC代码
#include<bits/stdc++.h>
using namespace std;
const int N = 1010;
const double det = 0.00001;
struct node {
int x, y;
} po[N]; // point of input
int upx, lox, upy, loy, n, ans;
int check(double x, double y) {
bool flag = false;
for(int i = 1, j = n;i <= n;j = i++) {
double x1 = po[i].x, y1 = po[i].y, x2 = po[j].x, y2 = po[j].y;
if(min(y1, y2) < y && y < max(y1, y2)) { // y在y1~y2或y2~y1的范围内,无需加上等号,因为y1、y2一定是整数,而y是整数经过±det,因此一定不等
double tx = (x2-x1)*(y-y1)/(y2-y1)+x1; // 根据两点式求出(x1, y1),(x2, y2)构成的直线上y对应的横坐标tx
if(x > tx) flag = !flag; // 若直线在在点左侧,则统计 // 我是向左做的射线,每穿过一条直线取反一次,相当于奇偶了
}
}
return flag;
}
int main()
{
cin>>n;
for(int i = 1;i <= n;i ++) {
cin>>po[i].x>>po[i].y;
upx = max(upx, po[i].x); // up_bound of x
lox = min(lox, po[i].x); // low_bound of x
upy = max(upy, po[i].y); // same as x
loy = min(loy, po[i].y);
}
for(int i = lox;i < upx;i ++)
for(int j = loy;j < upy;j ++) { // 下面中传入check的参数必须要经过±det
if( check(i+det, j+det) &&
check(i+1-det, j+det) &&
check(i+det, j+1-det) &&
check(i+1-det, j+1-det))
ans ++;
}
cout << ans << endl;
return 0;
}