题目描述
平面上有一些点,你可以用直线将两点连接起来。那么有多少种方法可以把这些点连续地连起来,使得任何两个线都不交叉。
显然,三个点只有一种方法。四个点最多只有 3 种方法。写一个程序计算方法总数。
输入格式
每一行是一个点的坐标,坐标值是整数,中间用一个空格隔开。最后一个坐标是原点。任意三点不在一条直线上。
输出格式
输出方案总数。
输入输出样例
输入 #1复制
100 -10 -200 0 45 7 0 0
输出 #1复制
3
说明/提示
最多只有 10 个点。
-
必须从一个点出发,途径所有点回到起点的路径才会被统计。
-
两个方案不相同当且仅当围成的简单多边形不同。
暴搜出奇迹
不要去想什么高深的算法了,也不要去推奇奇怪怪的数学公式,先想想计算机的发明是为了什么吧。
解:
本题的搜索方式不难想到,即每次枚举一点与当前点连线,然后判断这条是否与已连的线相交。所以本题的唯一难点是判断线段相交。
这里需要运用向量叉积。 <baidu>
简单来说:两个向量a和b的向量积是一个向量,记作a×b,其模等于由a和b作成的平行四边形的面积,方向与平行四边形所在平面平面垂直,当站在这个方向观察时,a逆时针转过一个小于π的角到达b的方向。这个方向也可以用物理上的右手螺旋定则判断:右手四指弯向由a转到b的方向(转过的角小于π),拇指指向的就是向量积的方向:从被乘数抓向乘数。如下图:
设向量P=(x1,y1),Q=(x2,y2),则向量a与向量b的叉积仍是一个向量,也可把叉积定义为一个矩阵行列式:
这时,结合定义,可得:
①P×Q>0;则P在Q的顺时针方向;
②P×Q<0;则P在Q的逆时针方向;
③P×Q=0;则P与Q共线,但可能同向也可能反向;
对于本题
两条线段AB、CD,相交的充要条件是:A、B在直线CD的异侧且C、D在直线AB的异侧。也就是说从AC到AD的方向与从BC到BD的方向不同,从CA到CB的方向与从DA到DB的方向也不同。这样就可以通过矢量的叉积来判断两线段是否相交:(AC×AD)∗(BC×BD)<0 且 (CA×CB)∗(DA×DB)<0 (具体实现见代码AddCross函数)
完
Talk is cheap, show me the code
#include<bits/stdc++.h>
#define re register
#define mn 15
using namespace std;
int n=1;
int ans;
struct dr{
double x,y;
} q[mn];
int v[mn]; //保存已选路径
bool vis[mn];
inline double Cross(dr a,dr b,dr c){
return (a.x-c.x)*(b.y-c.y)-(a.y-c.y)*(b.x-c.x);
}
inline bool AC(dr a,dr b,dr c,dr d){
return (Cross(c,d,a)*Cross(c,d,b)<0&&Cross(a,b,c)*Cross(a,b,d)<0);
}
inline bool Judge(int num,int b){
for(re int i=2;i<num-1;i++){
if(AC(q[v[i-1]],q[v[i]],q[v[num-1]],q[b])) return false;
}
return true;
}
void Dfs(int num){
if(num==n+1){
if(Judge(num,1)) ans++;
return;
}
for(re int i=2;i<=n;i++){
if(!vis[i]&&Judge(num,i)){
vis[i]=1;
v[num]=i;
Dfs(num+1);
vis[i]=0;
}
}
}
int main(){
while(true){
scanf("%lf %lf",&q[n].x,&q[n].y);
if(q[n].x||q[n].y) n++;
else break;
}
v[1]=1;
Dfs(2);
printf("%d",ans/2); //正序和倒序算一种情况
return 0;
}