【题解】 [SCOI2003]切割多边形

Description

有一个凸p边形(p<=8),我们希望通过切割得到它。一开始的时候,你有一个n*m的矩形,即它的四角的坐标分别为(0,0), (0,m), (n,0), (n,m)。每次你可以选择一条直线把当前图形切割成两部分,保留其中一个部分(另一部分扔掉)切割线的长度为此直线在多边形内部的部分的长度。求出最短的切割线总长度。下面是一个例子。我们需要得到中间的多边形。 分别沿着直线1,2,3,4进行切割即可,得到中间的四边形。

Input

第一行有两个整数n, m(0 < n,m < 500),第二行为一个整数p(3<=p<=8)。以下p行每行为两个整数x, y(0 < x < n, 0 < y < m),为按顺时针给出的各顶点坐标。数据保证多边形的是凸的,无三点共线。输入数据无错误。

Output

仅一行,为最短切割线的总长度,四舍五入到小数点后3位。允许有0.001的误差。

Sample Input

100 100
4
80 80
70 30
20 20
20 80

Sample Output

312.575

HINT

样例对应于图中给出的例子。

--------------------------------------------------------------------------------------------

发现这道题网上还没有题解。。我就来写第一篇啦

这道题一看就是几何题。。

p个顶点即有p条边,考虑到p的范围这么小,我们可以直接用搜索来尝试所有的切割顺序。。(很容易想到,这里的切割一定是沿着p边形的边切,否则白白增加了总长度,没有意义)

关键的问题就是如何计算出每次切割的长度,在这里我用了解析几何的方法。(用解析几何感觉好像挺非主流的,我觉得应该有计算几何的方法,如果有神牛知道求赐教!!)

先预处理p条边所在直线的方程式(联立方程组计算啦~),其中有一些边与矩形边界重合,不需要切,就先标记上。

计算切某条边所需的长度时,求出此边所在直线与所有已切边所在直线以及矩形边界的交点(又是联立方程组),分别记录这条边的某个点两端的交点距此点距离的最小值,它们的和就是本次切割的线的长度。

具体看代码吧,如果有什么地方不明白可以给我留言哦

#include<cstdio>
#include<cmath>
#include<cstring>
#include<cstring>
#include<algorithm>
#include<iostream>
#define Z Line
using namespace std;
const double eps=1e-6;
int xxx=0;
double ans=1e20;
struct Line
{
	double a,b,c;
}line[10+5];
int n,m;
struct Point
{
	int x,y;
}point[10+5];
bool use[10+5],flag[10+5];
int p;
bool count(double &x,double &y,const Z &m1,const Z &m2)//计算方程组的解 
{
	double d=m1.a*m2.b-m2.a*m1.b;
	if(fabs(d)<eps)return false;
	x=(m1.b*m2.c-m2.b*m1.c)/d;
	y=(m1.c*m2.a-m2.c*m1.a)/d;
	return true;
}
int pre(int i)
{
	if(i==1)return p;
	else return i-1;
}
double dis(double x1,double y1,double x2,double y2)
{
	return sqrt((x1-x2)*(x1-x2)+(y1-y2)*(y1-y2));
}
void calc2(double &best1,double &best2,double x,double y,int i)
{
		if(x<point[i].x-eps)
		{
			best1=min(best1,dis(point[i].x,point[i].y,x,y));
		}
		else if(x>point[i].x+eps)
		{
			best2=min(best2,dis(point[i].x,point[i].y,x,y));
		}
		else
		{
			if(y>point[i].y+eps)
			{
				best1=min(best1,dis(point[i].x,point[i].y,x,y));
			}
			else if(y<point[i].y-eps)
			{
				best2=min(best2,dis(point[i].x,point[i].y,x,y));
			}
			else
			{
				if(point[pre(i)].x>x+eps)best1=min(best1,dis(point[i].x,point[i].y,x,y));
				else if(point[pre(i)].x<x-eps)best2=min(best2,dis(point[i].x,point[i].y,x,y));
				else
				{
					if(point[pre(i)].y<y-eps)best1=min(best1,dis(point[i].x,point[i].y,x,y));
					else best2=min(best2,dis(point[i].x,point[i].y,x,y));
				}
			}
		}
}
double calc(int i)
{
	double best1,best2;//分别表示点point[i]左边的最短距离和右边的最短距离。。。如果分不出左右就是上下 
	best1=best2=1e20;
	double x,y;
	if(count(x,y,(Z){1,0,0},line[i]))
	{
		calc2(best1,best2,x,y,i);
	}
	if(count(x,y,(Z){1,0,-n},line[i]))
	{
		calc2(best1,best2,x,y,i);
	}
	if(count(x,y,(Z){0,1,0},line[i]))
	{
		calc2(best1,best2,x,y,i);
	}
	if(count(x,y,(Z){0,1,-m},line[i]))
	{
		calc2(best1,best2,x,y,i);
	}
	for(int j=1;j<=p;j++)
	{
		if(use[j]&&!flag[j])
		{
			if(count(x,y,line[j],line[i]))
			{
				calc2(best1,best2,x,y,i);
			}
		}
	}
	return best1+best2;
}
void dfs(int now,double sum)
{
	if(now==p)
	{
		ans=min(ans,sum);
		return;
	}
	for(int i=1;i<=p;i++)
	{
		if(!use[i])
		{
			double lenth=calc(i);
			use[i]=true;
			dfs(now+1,sum+lenth);
			use[i]=false;
		}
	}
}
int main()
{
	scanf("%d%d",&n,&m);
	scanf("%d",&p);
	for(int i=1;i<=p;i++)
	{
		scanf("%d%d",&point[i].x,&point[i].y);
		if(i>1)
		{
			if((point[i].x==point[i-1].x&&point[i].x==n)||(point[i].y==point[i-1].y&&point[i].x==m)){use[i]=true,flag[i]=true,xxx++;}
			double k,b;
			Z m1={point[i].x,1,-point[i].y};
			Z m2={point[i-1].x,1,-point[i-1].y};
			if(!count(k,b,m1,m2))
			{
				if(point[i].x==point[i-1].x)line[i].a=1,line[i].b=0,line[i].c=-point[i-1].x;
				if(point[i].y==point[i-1].y)line[i].a=0,line[i].b=1,line[i].c=-point[i-1].y;
			}
			else
			{
				line[i].a=k;line[i].b=-1;line[i].c=b;
			}
		}
	}
	if((point[1].x==point[p].x&&point[1].x==n)||(point[1].y==point[p].y&&point[1].x==m))flag[1]=true,use[1]=true,xxx++;
	double k,b;
	Z m1={point[1].x,1,-point[1].y};
	Z m2={point[p].x,1,-point[p].y};
	if(!count(k,b,m1,m2))
	{
		if(point[1].x==point[p].x)line[1].a=1,line[1].b=0,line[1].c=-point[p].x;
		if(point[1].y==point[p].y)line[1].a=0,line[1].b=1,line[1].c=-point[p].y;
	}
	else
	{
		line[1].a=k;line[1].b=-1;line[1].c=b;
	}
	if(xxx==p)
	{
		printf("%.3f\n",0);
		return 0;
	}
	for(int i=1;i<=p;i++)
	{
		if(!use[i])
		{
			double lenth=calc(i);
//			printf("%.3f\n",lenth);
			use[i]=true;
			dfs(xxx+1,lenth);
			use[i]=false;
		}
	}
	printf("%.3f\n",ans);
	return 0;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值