题目描述
平面上有一些点,你可以用直线将两点连接起来。那么有多少种方法可以把这些点连续地连起来,使得任何两个线都不交叉。
显然,三个点只有一种方法。四个点最多只有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;
}