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

题目描述

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

1n1000

这是一道计算几何题。

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

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

这样是 Θ(n3) 的。

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

image

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

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

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

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

然后对于 i ,将 fi,jfi,j+1 取更优的一个即可。

时间复杂度 Θ(n2logn)

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

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

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

image

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

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

  • 否则变成 2πatan2

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

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

#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;
}
posted @   The_Last_Candy  阅读(12)  评论(1编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!
点击右上角即可分享
微信分享提示