计算几何专题

4065. 【JSOI2015】投影面积(light)

Description
  • 给定N(≤10)条线段,线段分为不反光(光线射入后会被吸收)、双面反光(反射角等于入射角)。在所有线段上方有一条不反光的长线段。从原点按给定角度射出光束。求长线段被照亮的区域占其长度的比率。
  • 数据保证:坐标为绝对值不超1000的整数;所有线段不交,不经原点;对于一条光线,在其消失或被吸收之前,至多反射30次。
  • 答案保留4位小数。
Solution
  • 照亮区间有些难算,我们可以细分问题。
  • 发现线段数较少,坐标值较小,可以考虑将发出的光束大略视为极多条射线(比如500000条),然后分条模拟(毕竟至多反射30次),记录每条射线射到长线段的点。
  • 如果有一块区域的点较为密集,我们就认为这段区域都可以被光束射到,将这一段区域的长度一并计入答案。
Code
  • 然而并没有……╮(╯▽╰)╭
  • 我觉得这道题容易WA,懒得打了……(¦3」∠)

3856. 【NOIP2014八校联考第3场第1试10.4】规避(path)

Description
  • 给定N(≤100)个凸多边形,起点的编号和终点的编号,不能走进凸多边形内部,求最短路径长度。
  • 总点数M≤300。
Solution
  • 囿于是凸多边形,那肯定每次沿着当前凸多边形的某条边走,或者从当前点走向另一个凸多边形的一个顶点。
  • 那么考虑任意两点是否直接可达。
  • 显然,点x不能沿直线走到点y的充要条件是线段xy贯穿了某个凸多边形

  • 我们可以先将所有凸多边形的边(总共M条)存储下来。每次枚举两个点,判断其连线是否与那M条边有交点。
  • 但这样有一个问题:那就是可能有两个点的连线贯穿了一个凸多边形,但并未穿过该凸多边形的边,只是穿过了它的一个顶点。
  • 那我们可以加个小优化:不存凸多边形的边,而存它上面隔一个点的两个点连成的弦。显然这样也是只有M条边。
  • 接下来就是一个Floyd的事了。

  • 关于求线段是否有交的问题,博主在此口胡一下:
  • 我们先进行快速排斥实验,即判断以两条线段为对角线的矩形是否相交。 这玩意儿,我们对两端点的坐标值取个min、max,判判小于就好了。
  • 然后进行跨立实验,即用叉积判断线段a、b的两端点是否分列线段b、a的两侧。 那么显然一个左手向、一个右手向,叉积值乘起来<0即可。

  • 时间复杂度: O ( M 3 ) O(M^3) O(M3);空间复杂度: O ( M 2 ) O(M^2) O(M2)
Code
#include <bits/stdc++.h>
#define min(x,y) (x<y?x:y)
#define max(x,y) (x>y?x:y)
#define fo(i,a,b) for(i=a;i<=b;i++)
using namespace std;
typedef long double ld;

const int N=101,M=301;
int i,j,k,n,num[N],s[N],m,S,T;
ld f[M][M];
struct DOT
{
	ld x,y;
	inline DOT(){}
	inline DOT(ld _x,ld _y){x=_x,y=_y;}
	inline DOT operator+(const DOT a)const{return DOT(x+a.x,y+a.y);}
	inline DOT operator-(const DOT a)const{return DOT(x-a.x,y-a.y);}
}a[M],a1,a2,b1,b2;
struct LINE
{
	DOT p,v;
	inline LINE(){}
	inline LINE(DOT _p,DOT _v){p=_p,v=_v;}
}l[M],tmp;
inline ld dot(DOT a,DOT b) {return a.x*b.x+a.y*b.y;}
inline ld cro(DOT a,DOT b) {return a.x*b.y-a.y*b.x;}
inline ld len(DOT a) {return sqrt(dot(a,a));}
inline bool inter(LINE a,LINE b)
{
	a1=a.p, a2=a.p+a.v, b1=b.p, b2=b.p+b.v;
	return min(a1.x,a2.x)<max(b1.x,b2.x)&&min(a1.y,a2.y)<max(b1.y,b2.y)&&
		   min(b1.x,b2.x)<max(a1.x,a2.x)&&min(b1.y,b2.y)<max(a1.y,a2.y)&&
		   cro(a.v,b1-a.p)*cro(a.v,b2-a.p)<0&&cro(b.v,a1-b.p)*cro(b.v,a2-b.p)<0;
}

int main()
{
	scanf("%d",&n);
	fo(i,1,n)
	{
		s[i]=s[i-1]+num[i-1]; scanf("%d",&num[i]);
		fo(j,1,num[i]) 
		{
			scanf("%Lf%Lf",&a[s[i]+j].x,&a[s[i]+j].y);
			f[s[i]+j][s[i]+j]=0;
			if(j>2) l[s[i]+j]=LINE(a[s[i]+j],a[s[i]+j-2]-a[s[i]+j]);
		}
		l[s[i]+1]=LINE(a[s[i]+1],a[s[i]+num[i]-1]-a[s[i]+1]);
		l[s[i]+2]=LINE(a[s[i]+2],a[s[i]+num[i]  ]-a[s[i]+2]);
	}
	m=s[n]+num[n];
	fo(i,1,m-1)
		fo(j,i+1,m)
		{
			f[i][j]=f[j][i]=len(a[i]-a[j]);
			tmp=LINE(a[i],a[j]-a[i]);
			fo(k,1,m) 
				if(inter(tmp,l[k])) {f[i][j]=f[j][i]=1e20; break;}
		}
	fo(k,1,m)
		fo(i,1,m) if(i!=k)
			fo(j,1,m) if(i!=j&&j!=k&&f[i][j]>f[i][k]+f[k][j])
				f[i][j]=f[j][i]=f[i][k]+f[k][j];
	scanf("%d%d",&S,&T);
	printf("%.4Lf",f[S][T]); 
}

4187. 【NOI2015模拟7.9】激光发生器

Description
  • 给定一条射线和n(≤100)条线段,射线碰到线段会按照一定的偏转系数偏转。输出射线前10次碰到的线段的编号。
Solution
  • (假装正经)这道题确实就是道纯模拟大水题……
  • 我们循环10次,每次扫一遍所有线段,求一波这些线段与当前射线的交点,取与当前射线的起点距离最近的那个交点,该交点即为被射到的点。然后把该点作为新射线的起点,把射线的方向向量反过来,旋转一下,即可得到新射线的方向向量。
  • 求射线与线段的交点时,我是把射线转化为一条极长的线段,转化为求线段交点。(求线段交点的做法详见上题)注意本题射线平行射入线段并不发生偏转,所以如果这两条线段重合,我们判定为不交。 然后直接套直线交点坐标公式。
Code
#include <bits/stdc++.h>
#define y1 hoag
#define fo(i,a,b) for(i=a;i<=b;i++)
using namespace std;
typedef long double ld;

const int N=101;
const ld pi=acos(-1);
int i,j,n,Mj;
ld x1,y1,x2,y2,a,b,lam[N],l,Ml,alp,sA,cA;
struct DOT
{
	ld x,y;
	inline DOT(){}
	inline DOT(ld _x,ld _y){x=_x,y=_y;}
	inline DOT operator+(const DOT a)const{return DOT(x+a.x,y+a.y);}
	inline DOT operator-(const DOT a)const{return DOT(x-a.x,y-a.y);}
	inline DOT operator*(const ld lam)const{return DOT(x*lam,y*lam);}
}a1,a2,b1,b2,x,O,OA,OB;
struct LINE
{
	DOT p,v;
	inline LINE(){}
	inline LINE(DOT _p,DOT _v){p=_p,v=_v;}
}las,o[N];
inline ld dot(DOT a,DOT b) {return a.x*b.x+a.y*b.y;}
inline ld cro(DOT a,DOT b) {return a.x*b.y-a.y*b.x;}
inline ld len(DOT a) {return sqrt(dot(a,a));}
inline bool judge(LINE a,LINE b)
{
	a1=a.p, a2=a.p+a.v, b1=b.p, b2=b.p+b.v;
	return min(a1.x,a2.x)<max(b1.x,b2.x)&&min(a1.y,a2.y)<max(b1.y,b2.y)&&
		   min(b1.x,b2.x)<max(a1.x,a2.x)&&min(b1.y,b2.y)<max(a1.y,a2.y)&&
		   cro(a.v,b1-a.p)*cro(a.v,b2-a.p)<0&&cro(b.v,a1-b.p)*cro(b.v,a2-b.p)<0;
}
inline DOT inter(LINE a,LINE b) {return b.p+b.v*(cro(a.v,a.p-b.p)/cro(a.v,b.v));}
inline DOT rotate(DOT v,ld A)
{
	sA=sin(A),cA=cos(A);
	return DOT(v.x*cA-v.y*sA,v.x*sA+v.y*cA);
}
inline void C(ld &x) 
{
	if(abs(x)<1e-6) x=0;
} 

int main()
{
	freopen("laser.in","r",stdin);
	freopen("laser.out","w",stdout);
	scanf("%Lf%Lf%Lf%Lf%d",&las.p.x,&las.p.y,&las.v.x,&las.v.y,&n);
	fo(i,1,n)
	{
		scanf("%Lf%Lf%Lf%Lf%Lf%Lf",&x1,&y1,&x2,&y2,&a,&b);
		o[i]=LINE(DOT(x1,y1),DOT(x2-x1,y2-y1)); lam[i]=a/b;
	}
	fo(i,1,10)
	{
		C(las.p.x),C(las.p.y),C(las.v.x),C(las.v.y); 
		las.v=las.v*1e9;
		Ml=1e9;
		fo(j,1,n)
			if(judge(las,o[j]))
			{
				x=inter(las,o[j]); l=len(x-las.p);
				if(l>1e-6&&l<Ml) O=x,Ml=l,Mj=j;
			}
		if(Ml==1e9) {if(i==1) puts("NONE"); break;}
		printf("%d ",Mj);
		OA=las.p-O, OB=o[Mj].p-O, alp=abs(atan2(OA.y,OA.x)-atan2(OB.y,OB.x));
		if(alp>pi) alp=pi*2-alp;
		if(alp>pi/2) OB=o[Mj].p+o[Mj].v-O, alp=abs(atan2(OA.y,OA.x)-atan2(OB.y,OB.x));
		if(alp>pi) alp=pi*2-alp;
		alp=pi/2-alp;
		if(abs(alp-pi)>1e-6) OA=rotate(OA,(cro(OA,OB)<0?1:-1)*(lam[Mj]+1)*alp);
		las=LINE(O,OA);
	}
}

2881. 【SHTSC2012day1】信用卡凸包(card)

Description
  • 给定n个端点被坑成1/4圆的矩形,求其凸包周长。
Solution
  • Emmmmm……老实说,这题刚看毫无思路……是我太蠢了么……
  • 我们发现圆的凸包很难算,但仔细分析得知我们最终只会包裹一个圆的周长;然后其他部分的凸包都是直线。
  • 于是我们可以将矩形缩水,把它们的端点缩到对应的圆心位置,让它们变成“真·矩形”,然后直接上graham算法。最后答案再加回一个圆的周长。

  • 时间复杂度: O ( n log ⁡ 2 n ) O(n\log_2n) O(nlog2n);空间复杂度: O ( n ) O(n) O(n)
Code
#include <bits/stdc++.h>
#define fo(i,a,b) for(i=a;i<=b;i++)
using namespace std;
typedef double ld;

const int N=5e4;
const ld V[4][2]={{-1,-1},{-1,1},{1,-1},{1,1}},pi=acos(-1);
int i,j,n,top,cnt;
ld a,b,r,x,y,the,cA,sA,ans;
struct DOT
{
	ld x,y;
	inline DOT operator+(const DOT a)const{return {x+a.x,y+a.y};}
	inline DOT operator-(const DOT a)const{return {x-a.x,y-a.y};}
	inline ld  operator*(const DOT a)const{return  x*a.x+y*a.y ;}
	inline ld  operator^(const DOT a)const{return  x*a.y-y*a.x ;}
}d[N],sta[N],c;
inline ld dot(DOT a,DOT b) {return a.x*b.x+a.y*b.y;}
inline ld len(DOT a) {return sqrt(a*a);}
inline bool cmp(DOT a,DOT b) {return ((a-d[1])^(b-d[1]))>=0;}
inline DOT rotate(ld x,ld y,ld A)
{
	sA=sin(A),cA=cos(A);
	return {x*cA-y*sA,x*sA+y*cA};
}

int main()
{
	scanf("%d%lf%lf%lf",&n,&a,&b,&r); 
	fo(i,1,n)
	{
		scanf("%lf%lf%lf",&x,&y,&the); c={x,y};
		d[++cnt]=rotate(-b/2+r,-a/2+r,the)+c;
		d[++cnt]=rotate(-b/2+r,a/2-r,the)+c;
		d[++cnt]=rotate(b/2-r,-a/2+r,the)+c;
		d[++cnt]=rotate(b/2-r,a/2-r,the)+c;
	}
	n<<=2;
	fo(i,2,n) if(d[1].y>d[i].y) swap(d[1],d[i]);
	sort(d+2,d+n+1,cmp);
	sta[top=1]=d[1];
	fo(i,2,n)
	{
		while(top>1&&((sta[top]-sta[top-1])^(d[i]-sta[top-1]))<0) top--;
		sta[++top]=d[i];
	}
	fo(i,1,top) ans+=len(sta[i]-sta[i%top+1]);
	printf("%.2lf",ans+2*pi*r);
}

5094. 【GDSOI2017第四轮模拟day3】鸽子

Description
  • 给定m(≤500)个整点,可选任意个,使之构成的凸多边形包裹住给定的n(≤100000)个整点。求最少选点数。
  • 无解输出-1。
Solution
  • 有待填坑……

3865. 【JSOI2014】士兵部署

Description
  • 给定 N ( ≤ 1 0 5 ) N(≤10^5) N(105)个整点, M ( ≤ 1 0 5 ) M(≤10^5) M(105)次询问,每次询问假设加入一个整点后的凸包面积。
Solution
  • 这道题就和PKUWC的day2T3十分相像了。(虽然说我那题一分不得
  • 首先,加入一个点后的凸包一定不比原来的凸包小,故我们可以先用graham算法在 O ( n log ⁡ 2 n ) O(n\log_2n) O(nlog2n)的时间内对原图求凸包。
  • 考虑加入一个点后凸包面积的变化。如果这个点在凸包内,那么面积不变;否则,这个点将位于新的凸包上,并且使得原凸包的连续一段不再出现在凸包上(即过这个点作凸包的切线)。注意新凸包的面积总可以表示为原凸包上连续的一段顶点之间的叉积再加上新加入的点和两个原凸包上的点的叉积,为了快速求取面积,需要预处理叉积的前缀和。

  • 现在考虑如何判断一个点在已知凸包内。
  • 囿于凸包是一个凸多边形,我们可以预先将其分割为H-2个以凸包中第一个点为顶点的三角形(H为凸包上的点数)。
  • 询问时,二分出该点位于哪个三角形内(准确地说,是位于哪两条射线之间),如果在外部直接return;如果在两条射线之间,我们就用叉积判断该点是否在那个三角形内部。

  • 过一个点的凸包切线就更难求了。
  • 我的方法是要先二分出离该点最近、最远的点。但考虑到这个不是单调的,可以先把存储凸包的栈后移若干位,再额外二分一遍。
  • 求出最近、最远点后,令该两点连线,那么凸包便被分为上、下两部分。对其分别二分出切点即可。

  • 时间复杂度: O ( ( N + M ) log ⁡ 2 N ) O((N+M)\log_2N) O((N+M)log2N);空间复杂度: O ( N ) O(N) O(N)
Code
#include <bits/stdc++.h>
#define fo(i,a,b) for(i=a;i<=b;i++)
#define Make_mid(l,r) mid=(l<r?l+r>>1:(l+(top-l+r>>1)-1)%top+1)
using namespace std;
typedef long long ll;
typedef long double ld;

const int N=11e4;
int i,n,m,top,l,r,mid,tmp,Mn,Mx,L,R,m1;
ll sum[N],u,v,l1,l2,ans;
struct DOT
{
	ll x,y;
	inline DOT operator-(const DOT a)const{return{x-a.x,y-a.y};}
	inline ll  operator*(const DOT a)const{return x*a.x+y*a.y ;}
	inline ll  operator^(const DOT a)const{return x*a.y-y*a.x ;}
}d[N],sta[N],s1[N],X;
inline ll len(DOT a) {return a*a;}
inline bool cmp(DOT a,DOT b) {return ((a-d[1])^(b-d[1]))>0||((a-d[1])^(b-d[1]))==0&&len(a-d[1])<len(b-d[1]);}

int bs(DOT *s,bool k)
{
	l=1, r=top;
	while(l<r)
	{
		mid=l+r>>1;
		l1=len(s[mid]-X), l2=len(s[mid+1]-X);
		!k&&l1<l2||k&&l1>l2 ? r=mid : l=mid+1;
	}
	return l;
}

int main()
{
	freopen("soldier.in","r",stdin);
	freopen("soldier.out","w",stdout);
	scanf("%d%d",&n,&m);
	fo(i,1,n)
	{
		scanf("%lld%lld",&d[i].x,&d[i].y);
		if(d[1].y>d[i].y) swap(d[1],d[i]);
	}
	sort(d+2,d+n+1,cmp);
	fo(i,1,n)
	{
		while(top>1&&((sta[top]-sta[top-1])^(d[i]-sta[top-1]))<=0) top--;
		sta[++top]=d[i];
	}
	fo(i,1,top) 
	{
		sum[i]=sum[i-1]+(sta[i]^sta[i%top+1]);
		s1[(i+top/2)%top+1]=sta[i];
	}
	fo(i,1,m)
	{
		scanf("%lld%lld",&u,&v); X={u,v};
		
		l=3, r=top;
		while(l<r)
		{
			mid=l+r>>1;
			((sta[mid]-sta[1])^(X-sta[1]))<=0 ? r=mid : l=mid+1;
		}
		if(((sta[l-1]-sta[  1])^(X-sta[  1]))>=0&&
		   ((sta[l  ]-sta[l-1])^(X-sta[l-1]))>=0&&
		   ((sta[  1]-sta[l  ])^(X-sta[l  ]))>=0)
		   {printf("%.1Lf\n",(ld)sum[top]/2); continue;}
		
		Mn=bs(sta,0), tmp=bs(s1,0);
		if(len(s1[tmp]-X)<len(sta[Mn]-X)) Mn=(tmp-2+top-top/2)%top+1;
		Mx=bs(sta,1), tmp=bs(s1,1);
		if(len(s1[tmp]-X)>len(sta[Mx]-X)) Mx=(tmp-2+top-top/2)%top+1;
		
		L=Mx, r=Mn;
		while(L!=r)
		{
			Make_mid(L,r); m1=mid%top+1; 
			((sta[m1]-sta[mid])^(X-sta[mid]))>=0 ? L=m1 : r=mid;
		}
		l=Mn, R=Mx;
		while(l!=R)
		{
			Make_mid(l,R); mid=mid%top+1; m1=(mid-2+top)%top+1;
			((sta[m1]-sta[mid])^(X-sta[mid]))<=0 ? R=m1 : l=mid; 
		}
		
		ans=(L<R?sum[top]-sum[R-1]+sum[L-1]:sum[L-1]-sum[R-1]);
		printf("%.1Lf\n",(ld)(ans+abs((sta[L]^X)+(X^sta[R])))/2);
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值