P4529 [SCOI2003] 切割多边形

发布时间 2023-08-22 19:33:51作者: One_JuRuo

感觉这道题难点全在计算几何的细节,调了几天qwq。

思路

观察到 \(p\) 最大也只有 \(8\),作为蒟蒻的我第一时间就想到了暴力搜索,每次选一条没算过的边计算加进去的切割线长度。

有了核心思想,我们就要处理细节了,搜索很好写,重点是如何求出切割线。

在这里介绍两种方法:

第一种,我们暴力找到这条边与所有目前存在的边的交点,然后求出题目中给到的两个点 \(s,t\) 的中点 \(mid\)(其实只要是在线段 \(s,t\) 上的点都可以,这里是为了方便直接取得中点),因为题目保证要切割出来的多边形是凸多边形,所以所有线的交点必然在 \(s,t\) 的外面,而我们把找切割线的过程看做原有的线切新加的边,那么切割线应该是最里面的一段,所以我只需要找到能包含中点 \(mid\) 且离中点 \(mid\) 最近的两个点,再求距离即可。

如上图,切割线就是红色的线段。

那么,怎么求那两个点呢,我们可以对所有交点进行排序(排序可以对 \(x\) 或者 \(y\) 进行排序,只不过当线水平时不能以 \(y\) 排序,先竖直的时候不能以 \(x\) 排序),然后小于中点 \(mid\) 的所有点中最大的,和大于中点 \(mid\) 的所有点中最小的就是要求的那两个点。

第二种,假设这条边的两个点分别为 \(s,t\),那么我们只用求得射线 \(s,t\) 与所有存在的线的交点中离 \(t\) 最近的点和射线 \(t,s\) 与所有存在的线的交点中离 \(s\) 最近的点,再求两点距离即可。

AC代码

找切割线的方法用的第一种。

(注:代码包含了大部分计算几何的基础内容,所以有很多函数这道题没用,大佬看的时候可以自动忽略,好像只有主函数、搜索函数和求交点的函数用到了。

#include<bits/stdc++.h>
using namespace std;
const double eps=1e-8,pi=acos(-1.0);
const int N=100005;
/*这何尝不是一种火车头呢?*/
inline int dcmp(double x){return (x<-eps)?-1:(x>eps)?1:0;}//判断正负
inline double dabs(double x){return x*dcmp(x);}//绝对值 
/*向量(点)*/
struct vec{double x,y;vec(double _x=0,double _y=0){x=_x;y=_y;}};
inline bool cmp_vec(vec a,vec b){return (a.x!=b.x)?a.x<b.x:a.y<b.y;}
inline double len(vec a){return sqrt(a.x*a.x+a.y*a.y);}//模长 
inline double ang(vec a){return atan2(a.y,a.x);}//角度 
inline vec operator +(const vec &a,const vec &b){return vec(a.x+b.x,a.y+b.y);}
inline vec operator -(const vec &a,const vec &b){return vec(a.x-b.x,a.y-b.y);}
inline vec operator *(const vec &a,const double &b){return vec(a.x*b,a.y*b);}
inline vec operator /(const vec &a,const double &b){return vec(a.x/b,a.y/b);}
inline double operator *(const vec &a,const vec &b){return a.x*b.x+a.y*b.y;}//点积 
inline double operator ^(const vec &a,const vec &b){return a.x*b.y-a.y*b.x;}//叉积
inline vec rot(vec a,double the){return vec(a.x*cos(the)-a.y*sin(the),a.x*sin(the)+a.y*cos(the));}//旋转 
inline vec rot90(vec a){return vec(a.y,-a.x);}//特殊角旋转
inline vec rotpt(vec a,vec b,double the){return b+rot(a-b,the);}//a绕b旋转
/*线*/ 
struct line{vec s,t;line(vec _s=vec(0,0),vec _t=vec(0,0)){s=_s;t=_t;}};//线 
inline double maxx(line l){return max(l.s.x,l.t.x);}//大x
inline double maxy(line l){return max(l.s.y,l.t.y);}//大y 
inline double minx(line l){return min(l.s.x,l.t.x);}//小x
inline double miny(line l){return min(l.s.y,l.t.y);}//小y
inline double ang(line l){return ang(l.t-l.s);}//角度 
inline bool operator <(line &a,line &b){double a1=ang(a.t-a.s),a2=ang(b.t-b.s);return (dcmp(a2-a1))?a2>a1:dcmp((a.t-a.s)^(b.t-b.s))<0;} 
inline bool operator ==(vec &a,vec &b){return (!dcmp(a.x-b.x)&&!dcmp(a.y-b.y));}//点是否相等 
inline double dis(vec a,vec b){return sqrt((a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y));}//点距离
inline bool pd_pl(vec a,line b){return !dcmp((a-b.s)^(b.t-b.s));}//点是否在直线上
inline bool pd_px(vec a,line b){return pd_pl(a,b)&&(dcmp((a-b.s)*(a-b.t))<=0);}//点是否在线段上
inline vec footpt(vec a,line b){vec x=a-b.s,y=a-b.t,z=b.t-b.s;double s1=x*z,s2=-1.0*(y*z);return b.s+z*(s1/(s1+s2));}//求垂足(分别求as,at关于st的投影)
inline vec mirror(vec a,line b){return a+(footpt(a,b)-a)*2.0;}//a关于b的对称点
inline double dis_pl(vec a,line b){return dabs((a-b.s)^(a-b.t))/len(b.t-b.s);}//求a到直线b的距离(面积除以底边长)
inline double dis_px(vec a,line b){return (dcmp((a-b.s)*(b.t-b.s))<0)?len(a-b.s):(dcmp((a-b.t)*(b.t-b.s))>0)?len(a-b.t):dis_pl(a,b);}//求a到线段b的距离
inline vec pt_px(vec a,line b){return (dcmp((a-b.s)*(b.t-b.s))<0)?b.s:(dcmp((a-b.t)*(b.t-b.s))>0)?b.t:footpt(a,b);}//在线段b上与a最近的点
inline vec cross(line a,line b){vec x=a.t-a.s,y=b.t-b.s,z=a.s-b.s;return a.s+x*((y^z)/(x^y));}//求a与b的交点
inline bool pd_cross_lx(line a,line b){return (!dcmp((a.t-a.s)^(b.t-b.s)))?0:pd_px(cross(a,b),a);}//线段a与直线b是否相交
inline bool pd_cross_xx(line a,line b){return pd_cross_lx(a,b)&&pd_cross_lx(b,a);}//判断线段a是否与线段b相交
inline bool pd_cross_xx_(line a,line b)//判断两线段是否相交 (另一种方法,是否跨立) 
{
	if(maxx(a)<minx(b)||maxy(a)<miny(b)) return false;
	if(maxx(b)<minx(a)||maxy(b)<miny(a)) return false;
	double s1=(a.t-a.s)^(b.s-a.s),s2=(a.t-a.s)^(b.t-a.s);
	double s3=(b.t-b.s)^(a.s-b.s),s4=(b.t-b.s)^(a.t-b.s);
	return dcmp(s1)*dcmp(s2)<=0&&dcmp(s3)*dcmp(s4)<=0;//a的端点在b的两侧且b的端点在a的两侧 
}
inline double s_t(vec a,vec b,vec c){return dabs((b-a)*(c-a))/2;}//已知三点求三角形面积 
/*多边形*/
struct pol
{
	vector<vec> pt;
	inline vec& operator [](int x){return pt[x];}
	inline int ne(int x){return (x<pt.size()-1)?x+1:0;}
	inline void insert(vec p){pt.push_back(p);}
	inline void clear(){pt.clear();}
	inline int include(vec p)//点在多边形外:0,在多边形内:1,在多边形边上:2 
	{
		int cnt=0;
		for(int i=0;i<pt.size();i++)//射线法判断,射线方向为x轴正方向 
		{
			vec s=pt[i],t=pt[ne(i)];line l=line(s,t);
			if(pd_px(p,l)) return 2;
			if(dcmp(p.y-miny(l))>=0&&dcmp(maxy(l)-p.y)>0)
			if(dcmp(s.x+((p.y-s.y)/(t.y-s.y)*(t.x-s.x))-p.x)>0) cnt++;
		}
		return cnt&1;//偶数个交点则在多边形外,奇数个交点则在多边形内 
	}
	inline double s_p()//多边形面积 
	{
		double ans=0;
		for(int i=1;i<pt.size()-1;i++) ans+=(pt[i]-pt[0])^(pt[ne(i)]-pt[0]);
		return dabs(ans)/2;
	}
	inline double c_p()//多边形周长 
	{
		double ans=0;
		for(int i=0;i<pt.size();i++) ans+=dis(pt[i],pt[ne(i)]);
		return ans;
	}
	inline bool pd_l(vec x,vec l,vec r){return dcmp((l-x)^(r-x))<=0;}//xl是否在xr的左侧
	inline int include_(vec p)//二分法判断点是否在多边形内
	{
		int n=pt.size();
		if(!pd_l(pt[0],p,pt[1])) return 0;
		if(!pd_l(pt[0],pt[n-1],p)) return 0; 
		if(pd_px(p,line(pt[0],pt[1]))) return 2;
		if(pd_px(p,line(pt[0],pt[n-1]))) return 2;
		int l=1,r=n-2,ans=1,mid;
		while(l<=r)//二分找到最接近线(pt[0],p)的(pt[0],pt[ans]) 
		{
			mid=l+r>>1;
			if(!pd_l(pt[0],pt[mid],p)) l=mid+1,ans=mid;
			else r=mid-1;
		}
		if(!pd_l(pt[ans],p,pt[ne(ans)])) return 0;
		if(pd_px(p,line(pt[ans],pt[ne(ans)]))) return 2;
		return 1;
	} 
};
/*凸包*/
inline void andrew(pol &p)
{
	vector<vec>q(p.pt.size()<<1);int top=0,n=p.pt.size();
	sort(p.pt.begin(),p.pt.end(),cmp_vec),q[top]=p[0];
	for(int i=1;i<n;i++)
	{
		while(top&&dcmp((q[top-1]-q[top])^(q[top-1]-p[i]))<=0) top--;
		q[++top]=p[i];
	}
	int tot=top;
	for(int i=n-2;i>=0;i--)
	{
		while(top>tot&&dcmp((q[top-1]-q[top])^(q[top-1]-p[i]))<=0) top--;
		q[++top]=p[i];
	}
	if(n>1) top--;
	p.clear();
	for(int i=0;i<=top;i++) p.insert(q[i]);
}
/*旋转卡壳*/
inline double diam(pol p)
{
	int n=p.pt.size();double ans=0;
	for(int i=0,j=1;i<n;i++)
	{
		for(;;j=p.ne(j)) if(dcmp(s_t(p[j],p[i],p[p.ne(i)])-s_t(p[p.ne(j)],p[i],p[p.ne(i)]))>=0) break;
		ans=max(ans,max(dis(p[j],p[i]),dis(p[j],p[p.ne(i)])));
	}
	return ans;
}
/*main*/
int n,m,k;
double minn=100000;
vec a[15];
line l[10],bj[4];
bool st[10];
inline bool cmp_x(vec a,vec b){return a.x<b.x;}
inline bool cmp_y(vec a,vec b){return a.y<b.y;}
void dfs(int u,double ans)
{
	if(u==k){minn=min(minn,ans);return;}//更新答案
	for(int i=0;i<k;i++)//暴力找每条边
	{
		if(!st[i])
		{
			int cnt=0;
			vec p[20],mid=(l[i].s+l[i].t)/2.0,a,b;
			for(int j=0;j<4;j++) p[++cnt]=cross(l[i],bj[j]); 
			for(int j=0;j<k;j++) if(st[j]) p[++cnt]=cross(l[i],l[j]);//暴力求的当前边与所有存在的边的交点
			if(dcmp(p[1].x-p[cnt].x)==0)//如果是竖直的就以y排序(记得double的精度问题)
			{
				sort(p+1,p+cnt+1,cmp_y);//排序
				for(int i=1;i<=cnt;i++)
				{
					if(dcmp(p[i].y-mid.y)<0) a=p[i];//越靠后面的点y值更大,那么一直赋值直到出现y大于中点的情况,就可以找到第一个点
					else {b=p[i];break;}//第一个y大于中点的点就是我们要找的点
				}
			}
			else//以x排序的情况,与上面一种基本一样
			{
				sort(p+1,p+cnt+1,cmp_x);
				for(int i=1;i<=cnt;i++)
				{
					if(dcmp(p[i].x-mid.x)<0) a=p[i];
					else {b=p[i];break;}
				}
			}
			st[i]=1,dfs(u+1,ans+dis(a,b)),st[i]=0;//普普通通的dfs
		}
	}
}
int main()
{
	int a,b;pol p;
	scanf("%d%d%d",&n,&m,&k);
    bj[0]=line(vec(0,0),vec(0,m)),
    bj[1]=line(vec(n,0),vec(n,m)),
    bj[2]=line(vec(0,0),vec(n,0)),
    bj[3]=line(vec(0,m),vec(n,m));//要记得加入最开始的四条边
	for(int i=0;i<k;i++) scanf("%d%d",&a,&b),p.insert(vec(a,b));
	for(int i=0;i<k;i++) l[i]=line(p[i],p[p.ne(i)]);
	dfs(0,0);
	printf("%.3lf",minn);
}