【无标题】

题目描述

平面上有一些点,你可以用直线将两点连接起来。那么有多少种方法可以把这些点连续地连起来,使得任何两个线都不交叉。

显然,三个点只有一种方法。四个点最多只有33种方法。写一个程序计算方法总数。

输入格式

每一行是一个点的坐标,坐标值是整数,中间用一个空格隔开。最后一个坐标是原点。任意三点不在一条直线上。最多只有1010个点。

输出格式

输出,方案总数。

输入输出样例

输入 #1         输出 #1
100 -10         3
-200 0
45 7
0 0

这道题有很多方法,可以用凸包来做,也可以用深搜来做~~(因为数据实在是小的可怜)~~

前两篇题解用的深搜,搜索中有一个重要的内容就是判断两线段有没有交点的问题,这个其实不难

最简单易懂的方法就是:

线段的两端既然都知道了,轻轻松松就能算出两条线段所在直线的解析式,解析式求出来之后求两直线的交点,再和两条已知线段的端点进行比较,看是不是在端点限定的区间内就行了,这个方法代码简单,运算简便,简直有点简单的不像话,具体代码我就不贴了

现在来一发思维缜密的高大上的方法:向量叉乘

问题:给出两条线段,问两线段是否相交?

向量叉乘(行列式计算):向量a(x1,y1),向量b(x2,y2): 

首先我们要明白一个定理:向量a×向量b(×为向量叉乘),若结果小于0,表示向量b在向量a的顺时针方向;若结果大于0,表示向量b在向量a的逆时针方向;若等于0,表示向量a与向量b平行。(顺逆时针是指两向量平移至起点相连,从某个方向旋转到另一个向量小于180度)。如下图:

在上图中,OA×OB = 2 > 0, OB在OA的逆时针方向;OA×OC = -2 < 0,OC在OA的顺势针方向。即叉乘结果大于0,后一个在前一个的逆时针方向;小于零,后一个在前一个的顺时针方向。

那如何来判断两线段是否相交呢?

假设有两条线段AB,CD,若AB,CD相交,我们可以确定:

1.线段AB与CD所在的直线相交,即点A和点B分别在直线CD的两边;

2.线段CD与AB所在的直线相交,即点C和点D分别在直线AB的两边;

上面两个条件同时满足是两线段相交的充要条件,所以我们只需要证明点A和点B分别在直线CD的两边,点C和点D分别在直线AB的两边,这样便可以证明线段AB与CD相交了。

那判断两线段是否相交与一开始提到的向量叉乘定理有什么关系呢?有,我们可以通过叉乘来证明上面说的充要条件

在上图中,线段AB与线段CD相交,于是我们可以得到两个向量AC,AD,C和D分别在AB的两边,向量AC在向量AB的逆势针方向,AB×AC > 0;向量AD在向量AB的顺势针方向,AB×AD < 0,两叉乘结果异号。

这样,方法就出来了:如果线段CD的两个端点C和D,与另一条线段的一个端点(A或B,只能是其中一个)连成的向量,与向量AB做叉乘,若结果异号,表示C和D分别在直线AB的两边,若结果同号,则表示CD两点都在AB的一边,则肯定不相交。

当然,不能只证明C,D在直线AB的两边,还要用相同的方法证明A,B在直线CD的两边,两者同时满足才是线段相交的充要条件。

不过,线段相交还有一些特殊情况:

1.只有1点相交

上图中,线段AB与CD相交于C点,按照之前介绍的方法,我们可以连成两向量AD和AC,这时候,我们发现,AC与AB共线,AB×AC = 0;而AB×AD < 0;两者并不异号,可实际上仍然相交。所以当出现两叉乘结果中,有一方为0,也可以看成点CD在直线AB的两边。

2.两条线段重合

在上图中,线段AB与线段CD重合,重合部分为CB,这种重合的情况要特殊判断:

首先,我们给没条线段的两个端点排序,大小判断方法如下:横坐标大的点更大,横坐标相同,纵坐标大的点更大。

排好序后,每条线段中,小的点当起点,大的当终点。我们计算向量AB×向量CD,若结果为0,表示线段AB平行CD,平行才有了重合的可能;但平行也分共线和不共线,只有共线才有可能重合,看下图:

上图中,第一种情况不共线,第二种情况共线。那如何来判断是否共线呢?

我们可以在两条线段中各取一点,用这两点组成的向量与其中一条线段进行叉乘,结果若为0,就表示两线段共线

我们取向量BC,若BC×CD = 0,表示两点共线,即是第二种情况,否则就是第一种情况。第一种情况肯定不相交。猴子为什么不喜欢平行线?因为他们没有相交。。。(尬)

然然然然然而,即使他们共线,却还是不一定重合,就如上图中第二种情况。这时候,之前给点排序的妙处就体现出来了:

若一条线段AB与另一条线段CD共线,且线段AB的起点小于等于线段CD的起点,但线段AB的终点(注意是终点)大于等于线段CD的起点(注意是起点),或者交换一下顺序,CD的起点小于AB的起点......只要满足其中一个,就表示有重合部分。

代码:

#include <cstdio>
#include <algorithm>

using namespace std;

struct point{
	double x,y;
}po[5],map[11];
point a,b,c,d;

int n=1,ans;
int path[11];
bool vis[11];
bool disallow[11][11][11][11];

inline int compare(point a,point b)
{
	if(a.x<b.x||a.x==b.x&&a.y<b.y)  return -1;
	else if(a.x==b.x&&a.y==b.y) return 0;
	else return 1;
}

inline bool cmp(point a,point b)
{
	if(a.x!=b.x) return a.x<b.x;
	else return a.y<b.y;
}

inline double compute(double x1,double y1,double x2,double y2)
{
	return x1*y2-x2*y1;
}

bool judge(point a,point b,point c,point d)
{
	po[1]=a;po[2]=b;
	po[3]=c;po[4]=d;
	sort(po+1,po+3,cmp);
	sort(po+3,po+5,cmp);
	
	if(compute(po[1].x-po[2].x,po[1].y-po[2].y,po[3].x-po[4].x,po[3].y-po[4].y)==0) //两线段平行 
	{
		if(compute(po[1].x-po[2].x,po[1].y-po[2].y,po[1].x-po[3].x,po[1].y-po[3].y)==0) //两线段共线 
		{
			if(compare(po[1],po[3])<=0&&compare(po[2],po[3])>=0)  //第一条起点小于第二条起点,第一条终点大于第二条起点 
			  return true;
			else if(compare(po[3],po[1])<=0&&compare(po[4],po[1])>=0) //第二条起点小于第一条起点,第二条终点大于第一条起点
			  return true;
			else return false;
		}
		else return false;
	}
	else if(compute(po[1].x-po[2].x,po[1].y-po[2].y,po[3].x-po[4].x,po[3].y-po[4].y)!=0) //两线段不平行 
	{
		double num1,num2,num3,num4;
		num1=compute(po[1].x-po[2].x,po[1].y-po[2].y,po[1].x-po[3].x,po[1].y-po[3].y);//判断第一条线的两端点 
		num2=compute(po[1].x-po[2].x,po[1].y-po[2].y,po[1].x-po[4].x,po[1].y-po[4].y);//在第二条线的两边 
		num3=compute(po[1].x-po[3].x,po[1].y-po[3].y,po[3].x-po[4].x,po[3].y-po[4].y);//判断第二条线的两端点 
		num4=compute(po[2].x-po[3].x,po[2].y-po[3].y,po[3].x-po[4].x,po[3].y-po[4].y);//在第一条线的两边 
		if(num1*num2<=0&&num3*num4<0||num1*num2<0&&num3*num4<=0) //等于0表示一条线段的某一端点在另一条线段上
		  return true;
		else return false;
	}
	else return false;
}

void dfs(int step,int now)
{
	if(step==n+1) {
	ans++;
//	
//	for(int i=0;i<=step;i++)
//	  printf("%d ",path[i]);
//	  printf("\n");
//
	return;}
	if(step==n) vis[1]=false;
	for(int i=1;i<=n;i++)
	{
		if(vis[i]) continue;
		bool flag=true;
		for(int j=0;j<step-2&&flag;j++)
		  if(disallow[path[j]][path[j+1]][now][i]) flag=false;
		if(flag)
		{
			path[step]=i;
			vis[i]=true;
			dfs(step+1,i);
			vis[i]=false;
		}
		vis[1]=true;
	}
}

void ready()
{
	for(int i=1;i<=n;i++)
	  for(int j=1;j<=n;j++)
	    for(int p=1;p<=n;p++)
	      for(int q=1;q<=n;q++)
	      	if(judge(map[i],map[j],map[p],map[q])) disallow[i][j][p][q]=true;
}

int main ()
{
	while(scanf("%lf%lf",&map[n].x,&map[n].y))
	{
		if(map[n].x!=0||map[n].y!=0) n++;
		else break;
	}
	path[0]=1;
	vis[1]=true;
	ready();
	dfs(1,1);
	printf("%d\n",ans/2);
	return 0;
}

  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值