萃香的新年宴会第二题题解#2封兽鵺的激光游戏
Wy12121212 · · 题解
萃香的新年宴会第二题题解#2封兽鵺的激光游戏
by Wy12121212
题目链接
题意:平面上有一些凸多边形(不超过10个),凸边形的边数均不超过五条,且开始时每个多边形会绕重心顺时针旋转90k°,现在你可以从某一给定点处引一条射线,射线碰到多边形一边时便会发生反射并删除这条边。
现在,你需要求出使反射总次数最接近k的最小出射角度(x轴正方向为0°,y轴正方向为90°)
本题做题难度: 省选?
本题思考难度: 普及+/提高-
考查知识点:计算几何,码力,细节处理
我先说一下吧,这道题确实挺神仙的,如果你实在不想写copy我的代码我也不反对,当然如果你执意要写我也很赞成qwq
看到这道题时你可能会蒙住,所以让我们一步一步来分析吧
1.求重心:
很简单的第一步,只要把多边形划分成很多个三角形逐个求就可以了(具体详解请百度,这里不会浪费太多篇幅)
这里就是第一步的代码啦:
//求重心
struct Point
{
double x,y;
Point(double x = 0, double y = 0) : x(x),y(y) {};
void read()
{
scanf("%lf %lf",&x,&y);
}
Point operator - (const Point &a) const
{
return Point(a.x - x, a.y - y);
}
}a[1010][10],g[1010],yuanxin[1010];
double cross(Point A, Point B)
{
return A.x * B.y - A.y * B.x;
}
double area(Point a, Point b, Point c)
{
Point A,B;
A=b-a;
B=c-a;
return cross(A,B);
}
for(int _=1;_<=n;_++)
{
scanf("%d",&m);
if(m==0)
{
yuanxin[_].read();
scanf("%lf",&r[_]);
}
Point p0,p1,p2;
double sum_x,sum_y,sum_area;
p0.read();
a[_][0].x=m;
a[_][1]=p0;
p1.read();
a[_][2]=p1;
sum_x=sum_y=sum_area=0.0;
for(int i=2;i<m;i++)
{
p2.read();
a[_][i+1]=p2;
double tp=area(p0,p1,p2);
sum_x+=(p0.x+p1.x+p2.x)*tp;
sum_y+=(p0.y+p1.y+p2.y)*tp;
sum_area+=tp;
p1=p2;
}
Point G(sum_x/sum_area/3.0,sum_y/sum_area/3.0);
g[_]=G;
}
2.旋转:
好的我们现在有了每个多边形的重心g[i],怎么将多边形旋转90k°呢?其实用初中全等之时便可以轻松解决
第二步的代码:
for(int i=1;i<=k;i++)
{
double delx=a[_][j].x-g[_].x;
double dely=a[_][j].y-g[_].y;
a[_][j].x=g[_].x+dely;
a[_][j].y=g[_].y-delx;
}
3.存储每条线段:
直接当成直线存下来就可以了,注意这里x,y,x1,y1是端点,a,b,c是解析参数
struct lazer
{
double x,y;
double a,b,c;
double x1,y1;
lazer(double a=0, double b=0,double c=0,double x=0,double y=0) : a(a),b(b),c(c),x(x),y(y) {};
};
if(j==a[_][0].x)
{
xianduan[++cnt].x=a[_][j].x;
xianduan[cnt].y=a[_][j].y;
xianduan[cnt].x1=a[_][1].x;
xianduan[cnt].y1=a[_][1].y;
xianduan[cnt].a=a[_][j].y-a[_][1].y;
xianduan[cnt].b=-1*(a[_][j].x-a[_][1].x);
xianduan[cnt].c=((a[_][j].x-a[_][1].x)*a[_][1].y)-(a[_][j].y-a[_][1].y)*a[_][1].x;
}
else
{
xianduan[++cnt].x=a[_][j].x;
xianduan[cnt].y=a[_][j].y;
xianduan[cnt].x1=a[_][j+1].x;
xianduan[cnt].y1=a[_][j+1].y;
xianduan[cnt].a=a[_][j].y-a[_][j+1].y;
xianduan[cnt].b=-1*(a[_][j].x-a[_][j+1].x);
xianduan[cnt].c=((a[_][j].x-a[_][j+1].x)*a[_][j+1].y)-(a[_][j].y-a[_][j+1].y)*a[_][j+1].x;
}
4.开始枚举每个角度射出激光:
用三角函数算出斜率和截距就可以了,这里我们采用端点-任一点的方式存储射线,但是有两点需要注意:
1.调用tan时要用i%180,不然会严重丢精
2.斜率不存在的时候要特判(写法较劣)
for(double i=0;i<=360.0;i+=0.01)
{
memset(shoot,0,sizeof(shoot));
lazer st;
st.x=x;
st.y=y;
double u=i;
if(u>180)u-=180;
if(u!=90&&u!=270)
st.a=tan((u)*Pi/180),st.b=-1,st.c=(y-x*tan((u)*Pi/180));
else
{
st.a=1;
st.b=0;
st.c=-1*x;
}
if(i==90||i==270)
{
if(i==90)st.x1=x,st.y1=y+10;
else st.x1=x,st.y1=y-10;
}
else
{
if((i>=0&&i<=90)||(i>=270&&i<=360))
st.x1=x+10,
st.y1=(-st.c-st.a*st.x1)/(st.b);
else
st.x1=x-10,
st.y1=(-st.c-st.a*st.x1)/(st.b);
}
}
5.判断射线与线段相交:
重头戏,当线段两个端点在射线异侧,且射线端点不位于交点与任一点之间的时候判断相交
Point inter(lazer a,lazer b)
{
double y=((a.a*b.c-b.a*a.c)/(b.a*a.b-a.a*b.b));
double x;
if(a.a!=0)x=(-1*a.b*y-a.c)/a.a;
else x=(-1*b.b*y-b.c)/b.a;
Point intersp(x,y);
return intersp;
}
bool updown(lazer a,Point b)
{
if((-1*a.c-a.a*b.x)/a.b>b.y)return 1;
if((-1*a.c-a.a*b.x)/a.b<b.y)return 0;
return 2;
}
bool isinter(lazer a,lazer xd)
{
Point tmp1(xd.x,xd.y);
Point tmp2(xd.x1,xd.y1);
if((updown(a,tmp1)^updown(a,tmp2))!=1)return 0;
Point tmp3=inter(a,xd);
if((a.x>=tmp3.x&&a.x<=a.x1)||(a.x<=tmp3.x&&a.x>=a.x1))return 0;
//cout<<" "<<tmp3.x<<" "<<a.x<<" "<<a.x1<<endl;
return 1;
}
6.直线反射:
取射线关于法线的对称点,然后将交点设为新的端点,对称点设为新的任一点即可
至于如何对称及反射后直线的方程最好手推一遍公式,代入就可以了
Point sym(Point a,lazer b)
{
if(b.a==0)
{
Point tmp(a.x,(-1*b.c/b.b)-(a.y-(-1*b.c/b.b)));
return tmp;
}
if(b.b==0)
{
Point tmp((-1*b.c/b.a)-(a.x-(-1*b.c/b.a)),a.y);
return tmp;
}
lazer nl(b.b,-1*b.a,b.a*a.y-b.b*a.x);
Point tmpp=inter(nl,b);
double tmpx=2*tmpp.x-a.x;
double tmpy=2*tmpp.y-a.y;
Point tmp(tmpx,tmpy);
return tmp;
}
lazer bounce(lazer a,lazer b)
{
Point tmp1=inter(a,b);
if((-1*a.a/a.b)*(-1*b.a/b.b)==-1)
{
lazer nl(a.a,a.b,a.c,tmp1.x,tmp1.y);
return nl;
}
Point tmp2;
tmp2.x=a.x,tmp2.y=a.y;
Point tmp3;
if(b.a!=0&&b.b!=0)
{
lazer c(b.b/b.a,-1,tmp1.y-b.b*tmp1.x/b.a);
tmp3=sym(tmp2,c);
}
else
{
if(b.a==0)
{
lazer c(1,0,-1*tmp1.x);
tmp3=sym(tmp2,c);
}
else
{
lazer c(0,1,-1*tmp1.y);
tmp3=sym(tmp2,c);
}
}
lazer newlazer(tmp3.y-tmp1.y,-1*(tmp3.x-tmp1.x),((tmp3.x-tmp1.x)*tmp1.y)-(tmp3.y-tmp1.y)*tmp1.x,tmp1.x,tmp1.y);
newlazer.x1=tmp3.x;
newlazer.y1=tmp3.y;
return newlazer;
}
7.愉快地记录答案,输出,提交,然后ac
然后这是完整代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
using namespace std;
double Pi=3.141592653589793238462;
int n,m;
int w,E;
double x,y;
int cnt;
double ans;
int res=-1;
bool shoot[10010];
struct Point
{
double x,y;
Point(double x = 0, double y = 0) : x(x),y(y) {};
void read()
{
scanf("%lf %lf",&x,&y);
}
Point operator - (const Point &a) const
{
return Point(a.x - x, a.y - y);
}
}a[1010][10],g[1010],yuanxin[1010];
struct lazer
{
double x,y;
double a,b,c;
double x1,y1;
lazer(double a=0, double b=0,double c=0,double x=0,double y=0) : a(a),b(b),c(c),x(x),y(y) {};
};
lazer lastlazer;
lazer xianduan[10010];
double r[1010];
Point xianduana[10100],xianduanb[10100];
Point inter(lazer a,lazer b)
{
double y=((a.a*b.c-b.a*a.c)/(b.a*a.b-a.a*b.b));
double x;
if(a.a!=0)x=(-1*a.b*y-a.c)/a.a;
else x=(-1*b.b*y-b.c)/b.a;
Point intersp(x,y);
return intersp;
}
Point sym(Point a,lazer b)
{
if(b.a==0)
{
Point tmp(a.x,(-1*b.c/b.b)-(a.y-(-1*b.c/b.b)));
return tmp;
}
if(b.b==0)
{
Point tmp((-1*b.c/b.a)-(a.x-(-1*b.c/b.a)),a.y);
return tmp;
}
lazer nl(b.b,-1*b.a,b.a*a.y-b.b*a.x);
Point tmpp=inter(nl,b);
double tmpx=2*tmpp.x-a.x;
double tmpy=2*tmpp.y-a.y;
// cout<<tmpx<<" "<<tmpy<<endl;
Point tmp(tmpx,tmpy);
return tmp;
}
lazer bounce(lazer a,lazer b)
{
Point tmp1=inter(a,b);
// cout<<tmp1.x<<" "<<tmp1.y<<endl;
if((-1*a.a/a.b)*(-1*b.a/b.b)==-1)
{
lazer nl(a.a,a.b,a.c,tmp1.x,tmp1.y);
return nl;
}
Point tmp2;
tmp2.x=a.x,tmp2.y=a.y;
Point tmp3;
if(b.a!=0&&b.b!=0)
{
lazer c(b.b/b.a,-1,tmp1.y-b.b*tmp1.x/b.a);
tmp3=sym(tmp2,c);
}
else
{
if(b.a==0)
{
lazer c(1,0,-1*tmp1.x);
tmp3=sym(tmp2,c);
}
else
{
lazer c(0,1,-1*tmp1.y);
tmp3=sym(tmp2,c);
}
}
//if(a.a!=0)tmp2.x=(-1*a.b*(tmp1.y-1)-a.c)/a.a,tmp2.y=tmp1.y-1;
//else tmp2.y=-1*a.c/a.b,tmp2.x=tmp1.x-1;
// cout<<tmp3.x<<" "<<tmp3.y<<endl;
lazer newlazer(tmp3.y-tmp1.y,-1*(tmp3.x-tmp1.x),((tmp3.x-tmp1.x)*tmp1.y)-(tmp3.y-tmp1.y)*tmp1.x,tmp1.x,tmp1.y);
newlazer.x1=tmp3.x;
newlazer.y1=tmp3.y;
return newlazer;
}
double cross(Point A, Point B)
{
return A.x * B.y - A.y * B.x;
}
double area(Point a, Point b, Point c)
{
Point A,B;
A=b-a;
B=c-a;
return cross(A,B);
}
double dist(double x1,double y1,double x2,double y2)
{
return sqrt((y2-y1)*(y2-y1)+(x2-x1)*(x2-x1));
}
bool updown(lazer a,Point b)
{
if((-1*a.c-a.a*b.x)/a.b>b.y)return 1;
if((-1*a.c-a.a*b.x)/a.b<b.y)return 0;
return 2;
}
bool isinter(lazer a,lazer xd)
{
Point tmp1(xd.x,xd.y);
Point tmp2(xd.x1,xd.y1);
if((updown(a,tmp1)^updown(a,tmp2))!=1)return 0;
Point tmp3=inter(a,xd);
if((a.x>=tmp3.x&&a.x<=a.x1)||(a.x<=tmp3.x&&a.x>=a.x1))return 0;
//cout<<" "<<tmp3.x<<" "<<a.x<<" "<<a.x1<<endl;
return 1;
}
int main()
{
/*double xx1,yy1,xx2,yy2;
double a1,b1,c1;
cin>>a1>>b1>>c1;
cin>>xx1>>yy1>>xx2>>yy2;
lazer xx(a1,b1,c1,xx1,yy1);
xx.x1=xx2;
xx.y1=yy2;
cin>>a1>>b1>>c1;
cin>>xx1>>yy1>>xx2>>yy2;
lazer yy(a1,b1,c1,xx1,yy1);
yy.x1=xx2;
yy.y1=yy2;
cout<<isinter(xx,yy);*/
scanf("%d",&n);
for(int _=1;_<=n;_++)
{
scanf("%d",&m);
if(m==0)
{
yuanxin[_].read();
scanf("%lf",&r[_]);
}
Point p0,p1,p2;
double sum_x,sum_y,sum_area;
p0.read();
a[_][0].x=m;
a[_][1]=p0;
p1.read();
a[_][2]=p1;
sum_x=sum_y=sum_area=0.0;
for(int i=2;i<m;i++)
{
p2.read();
a[_][i+1]=p2;
double tp=area(p0,p1,p2);
sum_x+=(p0.x+p1.x+p2.x)*tp;
sum_y+=(p0.y+p1.y+p2.y)*tp;
sum_area+=tp;
p1=p2;
}
Point G(sum_x/sum_area/3.0,sum_y/sum_area/3.0);
g[_]=G;
}
for(int _=1;_<=n;_++)
{
int k;
scanf("%d",&k);
if(r[_]!=0)continue;
for(int j=1;j<=a[_][0].x;j++)
{
for(int i=1;i<=k;i++)
{
double delx=a[_][j].x-g[_].x;
double dely=a[_][j].y-g[_].y;
a[_][j].x=g[_].x+dely;
a[_][j].y=g[_].y-delx;
}
//cout<<a[_][j].x<<" "<<a[_][j].y<<endl;
if(j==a[_][0].x)
{
xianduan[++cnt].x=a[_][j].x;
xianduan[cnt].y=a[_][j].y;
xianduan[cnt].x1=a[_][1].x;
xianduan[cnt].y1=a[_][1].y;
xianduan[cnt].a=a[_][j].y-a[_][1].y;
xianduan[cnt].b=-1*(a[_][j].x-a[_][1].x);
xianduan[cnt].c=((a[_][j].x-a[_][1].x)*a[_][1].y)-(a[_][j].y-a[_][1].y)*a[_][1].x;
}
else
{
xianduan[++cnt].x=a[_][j].x;
xianduan[cnt].y=a[_][j].y;
xianduan[cnt].x1=a[_][j+1].x;
xianduan[cnt].y1=a[_][j+1].y;
xianduan[cnt].a=a[_][j].y-a[_][j+1].y;
xianduan[cnt].b=-1*(a[_][j].x-a[_][j+1].x);
xianduan[cnt].c=((a[_][j].x-a[_][j+1].x)*a[_][j+1].y)-(a[_][j].y-a[_][j+1].y)*a[_][j+1].x;
}
//xianduan[j]
}
}
scanf("%d%d",&w,&E);
scanf("%lf%lf",&x,&y);
for(double i=0;i<=360.0;i+=0.01)
{
memset(shoot,0,sizeof(shoot));
lazer st;
st.x=x;
st.y=y;
double u=i;
if(u>180)u-=180;
if(u!=90&&u!=270)
st.a=tan((u)*Pi/180),st.b=-1,st.c=(y-x*tan((u)*Pi/180));
else
{
st.a=1;
st.b=0;
st.c=-1*x;
}
if(i==90||i==270)
{
if(i==90)st.x1=x,st.y1=y+10;
else st.x1=x,st.y1=y-10;
}
else
{
if((i>=0&&i<=90)||(i>=270&&i<=360))
st.x1=x+10,
st.y1=(-st.c-st.a*st.x1)/(st.b);
else
st.x1=x-10,
st.y1=(-st.c-st.a*st.x1)/(st.b);
}
bool flag=0;
int cntt=0;
do
{
flag=0;
double minx=100000000;
int hehe=1;
for(int j=1;j<=cnt;j++)
{
if(!shoot[j]&&isinter(st,xianduan[j]))
{
flag=1;
Point tmmp=inter(st,xianduan[j]);
if(minx>dist(tmmp.x,tmmp.y,st.x,st.y))
{
minx=dist(tmmp.x,tmmp.y,st.x,st.y);
hehe=j;
}
}
}
if(flag==0)break;
shoot[hehe]=1;
cntt++;
st=bounce(st,xianduan[hehe]);
//cout<<hehe<<" "<<endl;
//cout<<st.a<<" "<<st.b<<" "<<st.c<<endl;
//cout<<st.x<<" "<<st.y<<" "<<st.x1<<" "<<st.y1<<endl;
}
while(1);
if(abs(w*cntt-E)<res||res==-1)
{
res=abs(w*cntt-E);
ans=i;
}
//if(cntt!=0)cout<<i<<" "<<cntt<<endl,getchar();
}
printf("%.2lf\n",ans);
return 0;
}
/*
2
4
0 0
0 4
4 4
4 0
3
8 0
12 4
12 0
0 0
1 2
5 5
*/
如果你有任何问题,请加入我的团队发帖提问或者加我的qq,私信我可能看不见