20200722 T2 ACT4!无限回转!【辛普森积分】

题目描述

n n n 个点的凸多边形,逆时针方向给出,在内部随机一个起点,随机一个方向,一直往这个方向走直到碰到某一条边。求每条边被碰到的概率。

n ≤ 1500 , ∣ x i ∣ , ∣ y i ∣ ≤ 1 0 4 n\le 1500,|x_i|,|y_i|\le 10^4 n1500,xi,yi104

题目分析

对于第 i i i 条边,求它被碰到的概率。

随机过程有两个,一个是坐标,一个是角度,考虑积分,最后除以 (面积 ∗ 2 π *2\pi 2π)

直接按坐标积分 d x , d y dx,dy dx,dy,用 a t a n atan atan 求角度范围,两层积分复杂度太高

按照中垂线确定角度大小,算出圆弧的长度,积分角度,算圆弧复杂度太高。

考虑对于一条边,确定一个角度,那么从这个方向可以碰到这条边的面积是这样的:

在这里插入图片描述

假设这个面积是 S ( θ ) S(\theta) S(θ),就是要求 ∫ S ( θ ) d θ \int S(\theta) d\theta S(θ)dθ

差分之后,就是求一条线右边与多边形交的面积。可以对其三角剖分,变成一个前缀和加上一个小三角形:

在这里插入图片描述

于是求一次的复杂度是 O ( log ⁡ n ) O(\log n) O(logn),然后就可以大力辛普森积分了。差分的减法可以直接利用下一个点的答案。

直接整可能会 T T T,需要一点常数优化,发现 n , ∣ x i ∣ , ∣ y i ∣ n,|x_i|,|y_i| n,xi,yi 越大,小块的面积影响就越小,所以当 n n n 大时 e p s eps eps 开小一点, n = 1500 n=1500 n=1500 e p s = 1 e − 2 eps=1e-2 eps=1e2 精度都很高。

复杂度 O ( n ∗ S i m p s o n ( e p s ) ∗ l o g ) O(n*Simpson(eps)*log) O(nSimpson(eps)log)

Code:

#include<bits/stdc++.h>
#define maxn 1505
using namespace std;
const double Pi = acos(-1);
int n;
double s[maxn],ans[maxn],lim[maxn],eps;
struct Point{
	double x,y; Point(double x=0,double y=0):x(x),y(y){}
	Point operator + (const Point &p){return Point(x+p.x,y+p.y);}
	Point operator - (const Point &p){return Point(x-p.x,y-p.y);}
	double operator * (const Point &p)const{return x*p.y-y*p.x;}
	double operator ^ (const Point &p)const{return x*p.x+y*p.y;}
	double len(){return sqrt(x*x+y*y);}
	bool operator < (const Point &p)const{return (*this)*p>0;}
}a[maxn*2],p[maxn],base;
double calc(double ang){
	double Sin=sin(ang),Cos=cos(ang);
	Point t = Point(base.x*Cos-base.y*Sin,base.y*Cos+base.x*Sin);
	int k = lower_bound(p+1,p+n,t)-p-1;
	return k==0 ? 0 : k==n-1 ? s[n-1] : s[k]+(p[k]*t)/(p[k]*t+t*p[k+1])*(p[k]*p[k+1]);
}
double Simpson(double l,double m,double r,double L,double M,double R,int dep){
	double m1=(l+m)/2,m2=(m+r)/2,M1=calc(m1),M2=calc(m2);
	double S=(L+4*M+R)/6*(r-l), S1=(L+4*M1+M)/6*(m-l), S2=(M+4*M2+R)/6*(r-m);
	if(fabs(S-S1-S2)<eps&&dep>=3) return S;
	return Simpson(l,m1,m,L,M1,M,dep+1)+Simpson(m,m2,r,M,M2,R,dep+1);
}
int main()
{
	freopen("tusk.in","r",stdin);
	freopen("tusk.out","w",stdout);
	scanf("%d",&n),eps=n<=5?1e-9:n<=100?1e-6:1e-2;
	for(int i=1;i<=n;i++) scanf("%lf%lf",&a[i].x,&a[i].y),a[n+i]=a[i];
	for(int i=1;i<=n;i++){
		for(int j=1;j<n;j++) p[j]=a[i+j]-a[i];
		for(int j=2;j<n;j++) s[j]=s[j-1]+p[j-1]*p[j];
		lim[i]=acos((p[1]^p[n-1])/p[1].len()/p[n-1].len());
		base=p[1];
		double L=calc(0),M=calc(lim[i]/2),R=calc(lim[i]);
		ans[i]=Simpson(0,lim[i]/2,lim[i],L,M,R,0);
	}
	double tmp=ans[1];
	for(int i=1;i<n;i++) ans[i]-=ans[i+1];
	ans[n]-=tmp;
	double S=0;
	for(int i=1;i<=n;i++) S+=a[i]*a[i+1];
	for(int i=1;i<=n;i++) ans[i]+=(Pi-lim[i])*S;
	for(int i=1;i<=n;i++) printf("%.10f\n",ans[i]/(2*Pi*S));
}

标算是再把面积分到每条边上,只需要积分那个小三角形的面积

在这里插入图片描述

在这里插入图片描述

还有一种做法是均分角度,每次将所有点旋转一个角度,然后每个点引一条射线,可以得到每个面积贡献到哪条边上,速度飞快,如图所示:

在这里插入图片描述
Code:

//code by dsfzsunjiawei
#include<cstdio>
#include<cstdlib>
#include<algorithm>
#include<ctime>
#include<cstring>
#include<random>
#include<cmath>
#define errlog(...) fprintf(stderr,__VA_ARGS__)
typedef long long ll;
typedef unsigned long long ull;
typedef double db;
std::mt19937 Rand((unsigned ll)(new int*));
inline ull Ranll()
{return(Rand()*(1llu<<32llu))+(Rand());}
inline ll random(ll l,ll r)
{return Ranll()%(r-l+1)+l;}
#define retrun return
#define cpnst const
int n;
int x[11111],y[11111];
struct pnt
{db x,y;pnt(db _x=0,db _y=0){x=_x,y=_y;}}r[11111];
db ans[11111];
db Fs(db L,db R,db H)
{return H*(R+L)/2;}
cpnst db eps=1e-8;
bool isequal(db x,db y){return x-y<eps&&y-x<eps;}
db Ht(pnt p1,pnt p2,db x)
{
	if(isequal(p1.x,x))return p1.y;
	if(isequal(p2.x,x))return p2.y;
	retrun p1.y+(p2.y-p1.y)*(x-p1.x)/(p2.x-p1.x);
}
void solve()
{
	register int i;
	db mnx=1e9;
	int mp=1,L,R;
	r[0]=r[n],r[n+1]=r[1];
	for(i=1;i<=n;i++)if(mnx>r[i].x)mnx=r[i].x,mp=i;
	db X=r[mp].x,YU=r[mp].y,YD=YU,Ls=0;
	L=mp,R=mp;
	for(i=2;i<=n;i++)
	{
		if(r[L-1].x<eps+r[R+1].x)
		{
			db det=r[L-1].y-Ht(r[R],r[R+1],r[L-1].x);
			ans[R]+=Fs(Ls,det,r[L-1].x-X);
			Ls=det,X=r[L-1].x,L=(L+n-2)%n+1;
		}else
		{
			db det=Ht(r[L],r[L-1],r[R+1].x)-r[R+1].y;
			ans[R]+=Fs(Ls,det,r[R+1].x-X);
			Ls=det,X=r[R+1].x,R=R%n+1;
		}
	}
}
void rota(db theta)
{
	register int i;
	db S=sin(theta),C=cos(theta);
	for(i=1;i<=n;i++)r[i]=pnt(r[i].x*C-r[i].y*S,r[i].y*C+r[i].x*S);
}
cpnst db pi=acos(-1);
int main()
{
	#ifndef StdIO
	freopen("tusk.in","r",stdin);
	freopen("tusk.out","w",stdout);
	#endif
	scanf("%d",&n);
	register int i;
	for(i=1;i<=n;i++)scanf("%d%d",x+i,y+i),r[i]=pnt(x[i],y[i]);
	for(i=1;i<=4954;i++)rota(pi/2477),solve();
	db SS=0;
	for(i=1;i<=n;i++)SS+=ans[i];
	for(i=1;i<=n;i++)printf("%.15lf\n",ans[i]/SS);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值