2020牛客暑期多校训练营Groundhog Playing Scissors(计算几何,线段交,暴力)

Groundhog Playing Scissors

题目描述

在这里插入图片描述

样例

input:
4
3 3
-3 3
-3 -3
3 -3
5
2 -1 2 1
output:
0.6098

题目大意

在坐标系中给出一个凸多边形,并可以绕原点旋转。现有一条线段,要求凸多边形旋转某个度数后,线段不能够将凸多边形截成两半(如果线段不够长就无法做到)的概率。

分析

很多dalao都是用三分做的,比较麻烦。这题有更加简洁的方式。

可以考虑精度误差。由于题目只需要我们保留4位小数,在时间充足的情况下,完全可以枚举。
枚举多边形转的度数 ,然后可以用计算几何判断一下就可以了。

然后就是转整个的多边形比较麻烦,所以可以转线段。而转线段我们可以理解成这条线段向原点的距离的线段旋转后,再在端点作垂线。

代码

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=7e4+10;
const double eps = 1e-8;
const double PI = acos(-1.0);
int sign(double x)
{
    if(fabs(x)<eps)return 0;
    if(x<0)return -1;
    else return 1;
}//判断正负
struct Point
{
    double x,y;
    Point(){}
    Point(double _x,double _y){x=_x;y=_y;}
    Point operator +(Point b){return Point(x+b.x,y+b.y);}
    Point operator -(Point b){return Point(x-b.x,y-b.y);}
    double operator ^(Point b){return x*b.y-y*b.x;}//叉积
    double operator *(Point b){return x*b.y+y*b.x;}//点积
    Point operator %(double a){return Point(x*a,y*a);}//数乘
    void input(){scanf("%lf%lf",&x,&y);}
};
struct Line
{
    Point s,e;
    Line(){}
    Line(Point _s,Point _e){s=_s;e=_e;}
    void linput(){s.input();e.input();}
};
Point rot(Point a,double Ct){
	return Point(a.x*cos(Ct)-a.y*sin(Ct),a.x*sin(Ct)+a.y*cos(Ct));
}//将点a绕着原点旋转Ct角度
Point inter(Line a,Line b){
	return a.s+(a.e-a.s)%(((b.e-b.s)^(b.s-a.s))/((b.e-b.s)^(a.e-a.s)));
}//求直线a,b的交点
double dist(Point a,Line b){
	Point v1=b.e-b.s,v2=a-b.s;
	return abs(v1^v2)/sqrt(v1.x*v1.x+v1.y*v1.y);
}//点a到直线b的距离
double norm(Point a){
	return sqrt(a.x*a.x+a.y*a.y);
}//模
Point vp[MAXN<<2];
int main()
{
	int n;scanf("%d",&n);
	for(int i=1;i<=n;i++){
		vp[i].input();vp[n+i]=vp[n*2+i]=vp[i];
		//这里要存3倍,枚举的时候可能跑了一圈,然后找相交的时候又跑了一圈,最后的指针可能落在第三圈
	}
	double d,len;scanf("%lf",&len);
	Line s;s.linput();
	d=dist(Point(0,0),s);//到原点的距离
	int cnt=0,tot=3e5,ptr1=1,ptr2=1;
	for(int i=0;i<tot;i++){
		double al=i*2*PI/tot;//旋转总共的1/tot角度
		Point cur=Point(cos(al),sin(al))%d;//旋转后的点,乘上数乘就是从原点向这个点作了垂线
		Point v=rot(cur,PI/2);//再旋转90°,这样可以复原出旋转后的线段
		Point nxt=cur+v;//注意上一行中只是向量,需要加上长度变成边
		Point p1,p2;Line ss=Line(cur,nxt);
		while(1){//判断与第一条边的相交
			int o1=sign((nxt-cur)^(vp[ptr1]-nxt));
			int o2=sign((nxt-cur)^(vp[ptr1+1]-nxt));//得出是否与这一条和下一条相交
			//当且仅当一条相交且下一条不相交时,才说明当前这条相交
			if(o1*o2==1){ptr1++;continue;}//否则继续寻找下一条边
			p1=inter(ss,Line(vp[ptr1],vp[ptr1+1]));//否则就记录交点,并退出
			break;
		}ptr2=max(ptr2,ptr1+1);//后面一条的编号要更大
		while(1){
			int o1=sign((nxt-cur)^(vp[ptr2]-nxt));
			int o2=sign((nxt-cur)^(vp[ptr2+1]-nxt));
			if(o1*o2==1){ptr2++;continue;}
			p2=inter(ss,Line(vp[ptr2],vp[ptr2+1]));
			break;
		}//同理继续找与第二条的相交
		double res=norm(p1-p2);//得出旋转后的相交的需要的长度
		if(sign(res-len)>0) cnt++;//如果不够长,则无法做到
	}printf("%.4f\n",1.0*cnt/tot);
}

END

暴力出奇迹

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值