FZU 1015 土地划分 (计算几何求线段交点个数)
传送门:FZU 1015
题目大意:求 L 条头尾相连的线段将矩形分成多少块。注意每个线段的端点都在矩形的边上,且不会有3线共点。
前置技能:计算几何判断两线段是否相交。这个直接套用模板就可以了。其思想是如果以两条线段为对角线的两矩形相交 且 两线段相互跨立,则两线段相交。
思路:这是《计算几何及应用》书上的一道例题。之前做过有道题和这个题很类似,是求 n 条直线最多能将平面分割成多少块。
可以把平面看成一个矩形,当有 1 条直线的时候,会分割成 2 块;当加一条直线的时候,变成了 4 块,这 4 块上部分(图中没有写字的部分)可以看作是之前形成的,而下半部分(写有红色标号的)可以看作加上这条直线后形成的。
当有 3 条直线的时候,如第 3 张图,未写有标号的部分可以看作是有 2 条直线的时候形成的,而写有标号的部分是加入第 3 条直线后形成的…… 依此类推,我们发现当加入第 n 条直线的时候,这条直线最多和 n-1 条直线相交,会多产生 n 个块。所以有 n 条直线时分成的块数 f ( n ) = f ( n-1 ) + n .
那么,本题是不是也有类似的规律呢?
我们发现,由于每个线段的端点都在矩形的边上,当有 L 条线段且都互不相交时,会把矩形分割成 L+1 块。在此基础上,如果有 n 条相交的线段,则会多产生 n 个块。如上图所示,未写有标号的块可以看作是没加入当前这条线段之前就有的,而写有标号的块是加入这条线段后产生的。
所以答案就是: L+1+n ,即 线段数 + 交点数 +1 。
这样一来,问题就转化成了求线段的交点个数的问题,可以将线段看成向量,用计算几何的方法直接有交点个数,直接套用模板就可以了。
注意:
1.这里的交点指的是完全相交,不包括一个线段的端点在另一个线段上的情况。
2.求向量的时候不可以将向量的起点移到源点,虽然两个向量相等但意义不一样。
代码:
#include<stdio.h>
#include<string.h>
struct point
{ //点
double x,y;
} a[55];
struct vct
{ //向量
point start,end; //起点和重点
} v[55];
int min(double a,double b)
{
return a<b? a:b;
}
int max(double a,double b)
{
return a>b? a:b;
}
/*以下两个函数作用为判断两线段是否相交*/
/*一个线段的一端在另一个线段上不算相交*/
double Multi(point p1,point p2,point p0)
{ //向量 p0p1 和 p0p2 相乘
return (p1.x-p0.x)*(p2.y-p0.y)-(p2.x-p0.x)*(p1.y-p0.y);
}
int Across(vct v1,vct v2)
{ //判断两线段是否相交
if(max(v1.start.x,v1.end.x)>=min(v2.start.x,v2.end.x)&&
max(v2.start.x,v2.end.x)>=min(v1.start.x,v1.end.x)&&
max(v1.start.y,v1.end.y)>=min(v2.start.y,v2.end.y)&&
Multi(v2.start,v1.end,v1.start)*Multi(v1.end,v2.end,v1.start)>0&&
Multi(v1.start,v2.end,v2.start)*Multi(v2.end,v1.end,v2.start)>0)
return 1;
return 0;
}
int main()
{
int i,j,w,h,L,ans;
while(~scanf("%d%d",&w,&h)&&w&&h)
{
scanf("%d",&L);
scanf("%lf%lf",&a[0].x,&a[0].y);
for(i=1;i<=L;i++)
{
scanf("%lf%lf",&a[i].x,&a[i].y);
v[i].start.x=a[i-1].x; //向量起点
v[i].start.y=a[i-1].y;
v[i].end.x=a[i].x; //向量终点
v[i].end.y=a[i].y;
}
ans=L+1;
for(i=1;i<=L;i++)
for(j=i+1;j<=L;j++)
{ //求交点个数
if(Across(v[i],v[j])) ans++;
}
printf("%d\n",ans);
}
return 0;
}