【数学】【计算几何】[POI2005] Dextrogyrate Camel 以及极角排序有关技巧

发布时间 2024-01-05 12:07:50作者: The_Last_Candy

题目描述

给定平面上 \(n\) 个点,从 \(1\) 号点出发,一开始朝向 \(2\) 号点,每次只能顺时针转 \([0^{\circ},180^{\circ}]\) 后前进到某个点,要求走一条每条边都不交(除了在端点处)路径,最后回到 \(1\) ,求最多能走过多少个不是 \(1\) 的点。

\(1 \leq n \leq 1000\)

这是一道计算几何题。

手模容易发现,如果你旋转的总角度超过了 \(360^{\circ}\) ,那么无论如何你都不能回到 \(1\) 点。所以要求就是只能顺时针转且总角度不超 \(360^{\circ}\) 。这启发我们以 \(1\) 号点为原点,以 \(2\) 号点为 \(0\) 度线对其他点进行极角排序,按照这个顺序计算(前面的一定不会依赖后面的)。

我们假设 \(f_{i,j}\) 为现在到点 \(i\) ,上一个是点 \(j\) 的最大点数,枚举前驱 \(k\) ,纸上手模发现合法条件是 \(k \to j\)\(j \to i\) 的左边,也就是 \((j \to i) \otimes (k \to j)\) (外积)要大于 \(0\)

这样是 \(\Theta(n^3)\) 的。

我们观察状态,发现同样是走过 \(j\) 个点,点 \(i\) 的前驱一定越 “左” 越好,也就是取所有前驱到 \(i\) 中向量,顺时针方向第一个,这样可以取到后面更多的点,如图,取 \(k\) 一定是最优的。

image

下面假设 “前驱” 就是上文说的最优的前驱。

前驱具有一定的单调性:假设现在在 \(i\) ,走过 \(j + 1\) 个点的前驱 \(k\) ,走过 \(j\) 个点的前驱 \(r\) ,一定满足 \(r \to i\)\(k \to i\) 的左边,不然用 \(k\) 就会严格优于 \(r\)

所以,我们可以利用这个二分。

\(f_{i,j}\) 表示在 \(i\) ,已经走过 \(j\) 个点的最优前驱,每次枚举 \(i\) 和转移点 \(k\) ,二分得到最大的 \(j\) 使得 \(f_{k,j} \to k\) 可以转到 \(k \to i\) ,用 \(k\) 更新 \(f_{i,j + 1}\) ,注意当 \(f_{i,j + 1}\) 有值时要比较一下。

然后对于 \(i\) ,将 \(f_{i,j}\)\(f_{i,j + 1}\) 取更优的一个即可。

时间复杂度 \(\Theta(n^2\log n)\)

现在具体讲一下如何以一个点为原点,一个点为零点极角排序。

首先我们将所有点的坐标减去 \(1\) 的坐标,这样相对位置不变。

观察 atan2 函数的值,发现是这样的:

image

我们想要一个从 \(x\) 正半轴开始,递增直到一圈的效果,所以分类:

  • 如果 atan2 为负数,则变成相反数。

  • 否则变成 \(2 \pi - atan2\)

这样就得到了一个从 \(x\) 正半轴开始递增的函数。

我们又想要它以 \(2\) 号点为零轴,直接将 atan2 减去 atan2(y2,x2) 后,跨过零点的值会出现问题,所以如果小于零,则加上 \(2\pi\) 即可。

#include<bits/stdc++.h>
using namespace std;
const int N = 1005;
typedef long double ld;
const ld PI = 3.141592653589793239;
struct Point{
	ld x,y,theta;
}a[N];
#define Vector Point
Vector operator -(Point p,Point q) 
{
	return Vector{q.x - p.x,q.y - p.y};
}
ld operator *(Vector p,Vector q)
{
	return p.x * q.y - p.y * q.x;
}
ld getan(ld y,ld x)
{
	ld ret = atan2(y,x);
	if(ret < 0) ret = -ret;
	else ret = 2 * PI - ret;
	return ret;
}
inline bool onleft(Vector p,Vector q) // p on the left of q?
{
	return (q * p >= 0);
}
int f[N][N],n;
int main()
{
	cin>>n;
	for(int i = 1;i <= n;i++) cin>>a[i].x>>a[i].y;
	for(int i = 2;i <= n;i++) a[i].x -= a[1].x,a[i].y -= a[1].y;
	a[1].x = a[1].y = 0.00;
	for(int i = 2;i <= n;i++) a[i].theta = (getan(a[i].y,a[i].x) - getan(a[2].y,a[2].x));
	for(int i = 3;i <= n;i++) if(a[i].theta < 0) a[i].theta += PI * 2;
	sort(a + 3,a + n + 1,[&](Point p,Point q) {return p.theta < q.theta;});
	for(int i = 1;i <= n;i++) for(int j = 1;j <= n;j++) f[i][j] = -1;
	f[2][1] = 1;
	int ans = 2;
	for(int i = 3;i <= n;i++)
	{
		for(int j = 2;j < i;j++)
		{
			if(!onleft(a[i] - a[j],a[1] - a[j])) continue;
			int l = 0,r = i - 2;
			while(l < r)
			{
				int mid = (l + r + 1) >> 1;
				if(f[j][mid] == -1) r = mid - 1;
				else
				{
					if(onleft(a[j] - a[f[j][mid]],a[i] - a[j])) l = mid;
					else r = mid - 1;
				}
			}
			if(l == 0) continue;
			if(f[i][l + 1] == -1) f[i][l + 1] = j;
			else
			{
				if(onleft(a[i] - a[j],a[i] - a[f[i][l + 1]])) 
					f[i][l + 1] = j;
			}
			ans = max(ans,l + 1);
		}
		for(int j = i - 1;j >= 1;j--)
		{
			if(f[i][j] == -1) f[i][j] = f[i][j + 1];
			else if(f[i][j + 1] != -1)
			{
				if(onleft(a[i] - a[f[i][j + 1]],a[i] - a[f[i][j]])) 
					f[i][j] = f[i][j + 1];
			}
		}
	}
	cout<<ans;
	return 0;
}