ZJNU 2021-07-21 专题二 计算几何 题解
除\(K\)外,其余题均已更新思路与代码
感觉除\(K\)题外其余题都可以补
A - Power Transmission (Easy Edition)
B - Power Transmission (Hard Edition)
来源
标签
直线方程、数论(\(double\)转分数型)
题意
平面上有\(n\ (2\le n\le 50,\ easy)(2\le n\le 1000,\ hard)\)个互不相同的整数点
每两个点都有一条直线连接
如果两条线重叠,那么当作只存在一条线
问有多少对直线相交?
思路
由于两条线重叠需要去重,最好的方式就是以斜率\(k\)与截距\(b\)的方式来描述直线\(y=kx+b\),然后借助\(map\)等容器实现去重
但由于\(double\)计算存在的精度差,可能本应重叠的两条直线因为精度问题被误判为不相同
或者是重写了直线的判定规则,但本应不重叠的两条直线因为限制问题被误判为不相同
所以这类问题的做法是将\(double\)类型的值转化成分数型来存储(这是种常见做法,本题的计算只有一次除法,处理起来很方便)
明显我们化简分子与分母的方法就是让两者都除以其最大公因数\(gcd\),使其化成最简;对于负数,强制保证分母是整数,负号交给分子
于是便可以通过两个整数来描述一个分数,避免了精度误差
对于本题,注意到相同斜率不同截距的直线间不会出现交点,所以借助\(map<set>\)的数据结构维护,\(map\)通过斜率来找出对应的\(set\),而\(set\)则维护截距是否重复
每次统计答案,只需加上当前出现过的直线数量减去与当前直线斜率相同的直线数量即可
代码
#include<bits/stdc++.h>
#define closeSync ios::sync_with_stdio(0);cin.tie(0);cout.tie(0)
using namespace std;
typedef pair<int,int> P;
typedef long long ll;
const int INF=0x3f3f3f3f;
const double eps=1e-12;
int dbcmp(double x){
if(fabs(x)<eps)
return 0;
return x<0?-1:1;
}
struct Point{
double x,y;
Point(){}
Point(double _x,double _y){
x=_x;
y=_y;
}
bool operator == (Point b)const{
return dbcmp(x-b.x)==0&&dbcmp(y-b.y)==0;
}
Point operator +(const Point &b)const{
return Point(x+b.x,y+b.y);
}
Point operator -(const Point &b)const{
return Point(x-b.x,y-b.y);
}
Point operator *(const double &k)const{
return Point(x*k,y*k);
}
Point operator /(const double &k)const{
return Point(x/k,y/k);
}
double operator *(const Point &b)const{ //点积
return x*b.x+y*b.y;
}
double operator ^(const Point &b)const{ //叉积
return x*b.y-y*b.x;
}
};
int gcd(int a,int b)
{
return !b?a:gcd(b,a%b);
}
void simplification(int &a,int &b)
{
int c=gcd(a,b);
a/=c,b/=c;
if(b<0)
a=-a,b=-b;
} //化简分式
Point pt[1050];
int main()
{
closeSync;
int n;
cin>>n;
for(int i=1;i<=n;i++)
cin>>pt[i].x>>pt[i].y;
map<P,set<P> > mp;
ll ans=0,cnt=0;
// 枚举每条直线
for(int i=1;i<=n;i++)
for(int j=i+1;j<=n;j++)
{
//特殊判断k=0与k=INF
if(pt[i].x==pt[j].x)
{
set<P> &st=mp[P(0,0)];
if(st.find(P(pt[i].x,pt[i].x))==st.end())
{ //如果这个截距未出现过
ans+=cnt-st.size(); //现总直线数-斜率相同直线数
st.insert(P(pt[i].x,pt[i].x));
cnt++;
}
}
else if(pt[i].y==pt[j].y)
{
set<P> &st=mp[P(0,1)];
if(st.find(P(pt[i].y,pt[i].y))==st.end())
{
ans+=cnt-st.size();
st.insert(P(pt[i].y,pt[i].y));
cnt++;
}
}
else
{
int dy,dx,b1,b2;
dy=pt[j].y-pt[i].y;
dx=pt[j].x-pt[i].x;
simplification(dy,dx);
b1=pt[i].y*dx-pt[i].x*dy;
b2=dx;
simplification(b1,b2);
set<P> &st=mp[P(dy,dx)];
if(st.find(P(b1,b2))==st.end())
{
ans+=cnt-st.size();
st.insert(P(b1,b2));
cnt++;
}
}
}
cout<<ans<<'\n';
return 0;
}
C - Rolling The Polygon
来源
标签
几何
题意
按照逆时针顺序给定一个凸多边形的\(n\ (3\le n\le 50)\)个顶点坐标\(P_0,P_1,\cdots,P_{n-1}\),再给定多边形内部一点\(Q\)
初始时\(P_0P_1\)边与地面接触
现要将这个凸多边形在地上无滑动地滚动一周
若当前时\(P_iP_{(i+1)\mod n}\)边与地面接触,则滚动一次后为\(P_{(i+1)\mod n}P_{(i+2)\mod n}\)与地面接触
问滚动一周后,\(Q\)点移动轨迹的长度
思路
我们考虑每一次反转,都是以多边形上某个顶点\(P\)作为圆心转动了一个圆弧
这个圆弧的半径即该点\(P\)到点\(Q\)的距离,而圆心角即多边形此时转动的角度,也就是与\(P\)相邻的两条边的夹角
利用\(\vec{v_1}\cdot\vec{v_2}=|\vec{v_1}|\cdot|\vec{v_2}|\cdot cos<\vec{v_1},\vec{v_2}>\)借助\(cos^{-1}\)函数求夹角\(\theta\),圆弧长即\(\theta\cdot r\)(弧度制)
代码
#include<cstdio>
#include<cmath>
#include<vector>
using namespace std;
typedef pair<int,int> P;
typedef long long ll;
const int INF=0x3f3f3f3f;
const double eps=1e-12;
const double PI=acos(-1.0);
int dbcmp(double x){
if(fabs(x)<eps)
return 0;
return x<0?-1:1;
}
struct Point{
double x,y;
Point(){}
Point(double _x,double _y){
x=_x;
y=_y;
}
Point operator +(const Point &b)const{
return Point(x+b.x,y+b.y);
}
Point operator -(const Point &b)const{
return Point(x-b.x,y-b.y);
}
double operator *(const Point &b)const{ //点积
return x*b.x+y*b.y;
}
double operator ^(const Point &b)const{ //叉积
return x*b.y-y*b.x;
}
double distance(Point p){
return sqrt((x-p.x)*(x-p.x)+(y-p.y)*(y-p.y));
}
double length(){
return sqrt(x*x+y*y);
}
};
typedef Point Vector;
Point p[55],q;
void solve(int cas)
{
int n;
scanf("%d",&n);
for(int i=0;i<=n;i++)
{
int x,y;
scanf("%d%d",&x,&y);
p[i]=Point(x,y);
}
q=p[n];
double ans=0.0;
for(int i=0;i<n;i++)
{ //处理的是点j
int j=(i+1)%n,k=(i+2)%n;
Vector v1=p[j]-p[i],v2=p[k]-p[j];
double r=q.distance(p[j]);
double deg=acos((v1*v2)/v1.length()/v2.length());
ans+=deg*r;
}
printf("Case #%d: %.3f\n",cas,ans);
}
int main()
{
int T;
scanf("%d",&T);
for(int t=1;t<=T;t++)
solve(t);
return 0;
}
D - Area
来源
标签
叉积的运用(计算面积)
题意
初始时你在原点\((0,0)\),给定一个行走顺序,保证在结束行走时你会回到原点,且行走的轨迹会围成一个多边形(轨迹不会出现交叉的情况)
问这个多边形的面积
思路
已知\((0,0)\)点一定在轨迹上,故以这点为基准点,借助三角形拆分,模拟行走的路线的前后两点,利用向量叉积计算解即可
注意根据方向的不同,面积可能是个负数,取反即可
基于某些迷幻的原因,本题最好不要直接输出double
代码
#include<iostream>
#define closeSync ios::sync_with_stdio(0);cin.tie(0);cout.tie(0)
using namespace std;
typedef pair<int,int> P;
typedef long long ll;
const int INF=0x3f3f3f3f;
const double eps=1e-12;
struct Point{
ll x,y;
Point(){}
Point(ll _x,ll _y){
x=_x;
y=_y;
}
ll operator ^(const Point &b)const{ //叉积
return x*b.y-y*b.x;
}
};
void solve()
{
string s;
cin>>s;
int len=s.size();
if(len==1)
{
cout<<"0\n";
return;
}
Point p=Point(0,0),q=Point(0,0);
ll ans=0;
for(int i=0;i<len;i++)
{
char c=s[i];
if(c=='1')
p.x++,p.y--;
else if(c=='3')
p.x++,p.y++;
else if(c=='7')
p.x--,p.y--;
else if(c=='9')
p.x--,p.y++;
else if(c=='2')
p.x++;
else if(c=='4')
p.y--;
else if(c=='6')
p.y++;
else if(c=='8')
p.x--;
else
break;
ans+=p^q;
q=p;
}
if(ans<0)
ans=-ans;
if(ans%2)
cout<<ans/2<<".5\n";
else
cout<<ans/2<<'\n';
}
int main()
{
closeSync;
int T;cin>>T;
while(T--)
solve();
return 0;
}
E - Pick-up sticks
来源
标签
叉积的运用(判断线段相交)
题意
按顺序往平面中放置线段
后放的线段会覆盖在之前的线段之上
问有多少条线段没有被其他任意线段覆盖
数据保证任意时刻没有被其他任意线段覆盖的线段最多只有\(1000\)条
思路
保证任意时刻没有被其他任意线段覆盖的线段最多只有\(1000\)条
可以考虑\(vector\)存储当前的合法线段,其后每一条加入的线段只要与这些线段进行叉积判断是否相交即可
数据量较大,不推荐使用\(iostream\)
代码
#include<cstdio>
#include<cmath>
#include<vector>
using namespace std;
typedef pair<int,int> P;
typedef long long ll;
const int INF=0x3f3f3f3f;
const double eps=1e-12;
int dbcmp(double x){
if(fabs(x)<eps)
return 0;
return x<0?-1:1;
}
struct Point{
double x,y;
Point(){}
Point(double _x,double _y){
x=_x;
y=_y;
}
Point operator +(const Point &b)const{
return Point(x+b.x,y+b.y);
}
Point operator -(const Point &b)const{
return Point(x-b.x,y-b.y);
}
double operator ^(const Point &b)const{ //叉积
return x*b.y-y*b.x;
}
};
typedef Point Vector;
struct Line{
Point s,e;
Line(){}
Line(Point _s,Point _e){
s=_s;
e=_e;
}
bool isSegmentCross(Line l){ //判断两线段是否相交
Vector v1=e-l.s,v2=s-l.s;
Vector v3=e-l.e,v4=s-l.e;
Vector v5=l.s-s,v6=l.e-s;
Vector v7=l.s-e,v8=l.e-e;
return dbcmp((v1^v2)*(v3^v4))<0&&dbcmp((v5^v6)*(v7^v8))<0;
}
};
Line line[100010];
void solve(int n)
{
vector<int> vec;
for(int i=1;i<=n;i++)
{
double x1,y1,x2,y2;
scanf("%lf%lf%lf%lf",&x1,&y1,&x2,&y2);
line[i]=Line(Point(x1,y1),Point(x2,y2));
for(int j=0;j<vec.size();j++)
{
if(line[i].isSegmentCross(line[vec[j]]))
{ //如果此时放的线段i与vec[j]相交,则vec[j]将不作为答案
vec.erase(vec.begin()+j);
j--;
}
}
vec.push_back(i);
}
printf("Top sticks: ");
for(int j=0;j<vec.size();j++)
printf("%d%s",vec[j],j+1==vec.size()?".\n":", ");
}
int main()
{
int n;
while(scanf("%d",&n)!=EOF)
{
if(!n)
break;
solve(n);
}
return 0;
}
F - TOYS
来源
标签
叉积的运用(点与直线的位置关系)、二分
题意
给定一个矩形的左上角与右下角坐标
有\(n\ (0\lt n\le 5000)\)条线段将这个矩形分割成\(n+1\)个区域(不存在两条线段相交),这\(n+1\)个区域从左往右编号\(0,1,\cdots,n\)
现在告诉你矩形中的\(m\ (0\lt m\le 5000)\)个点,要求输出每个区域中的点的数量
思路
由于每条线段都不会相交,所以每个点对于所有的线段来说,要么是在线段左侧,要么是在线段右侧
存在二分性,如果点在线段\(i\)的右侧,则答案区域编号\(j\ge i\);否则,答案区域编号\(j\lt i\)
定义每条线段与矩形接触的上端点为\(P\),下断点为\(Q\),当前询问的点为\(S\)
则考虑判断\(\vec{PQ}\times\vec{PS}\)叉积的正负性即可
代码
#include<cstdio>
#include<cmath>
#include<vector>
using namespace std;
typedef pair<int,int> P;
typedef long long ll;
const int INF=0x3f3f3f3f;
const double eps=1e-12;
int dbcmp(double x){
if(fabs(x)<eps)
return 0;
return x<0?-1:1;
}
struct Point{
double x,y;
Point(){}
Point(double _x,double _y){
x=_x;
y=_y;
}
Point operator +(const Point &b)const{
return Point(x+b.x,y+b.y);
}
Point operator -(const Point &b)const{
return Point(x-b.x,y-b.y);
}
double operator ^(const Point &b)const{ //叉积
return x*b.y-y*b.x;
}
};
typedef Point Vector;
//记录每条线的上端点与方向向量即可
Point pt[5010];
Vector vec[5010];
int cnt[5010];
void solve(int n)
{
int m,L,R,U,B;
scanf("%d%d%d%d%d",&m,&L,&U,&R,&B);
for(int i=1;i<=n;i++)
{
int x1,x2;
scanf("%d%d",&x1,&x2);
pt[i]=Point(x1,U);
vec[i]=Vector(x2-x1,B-U);
}
for(int i=0;i<=n;i++)
cnt[i]=0;
for(int i=1;i<=m;i++)
{
double x,y;
scanf("%lf%lf",&x,&y);
Point p=Point(x,y);
int l=1,r=n;
while(l<=r)
{
int m=l+r>>1;
Vector vec2=p-pt[m];
// 以两向量叉乘正负判断点与线段的位置
if(dbcmp(vec[m]^vec2)>0)
l=m+1;
else
r=m-1;
}
cnt[r]++;
}
for(int i=0;i<=n;i++)
printf("%d: %d\n",i,cnt[i]);
}
int main()
{
int n;
bool multiCase=false;
while(scanf("%d",&n)!=EOF)
{
if(!n)
break;
if(multiCase)
putchar('\n');
multiCase=true;
solve(n);
}
return 0;
}
G - Triangle
来源
标签
叉积的运用(求面积)、二分
题意
给定三角形的三个顶点\(A,B,C\)即三角形上一点\(P(p_x,p_y)\)
要求求出另外一点\(Q(q_x,q_y)\),使得由\((p_x,p_y)\)及\((q_x,q_y)\)两点连成的线段能够平分三角形的面积
思路
分情况进行讨论
首先判断点\(P\)位于三角形的哪一条边上;如果不在三角形上,输出\(-1\)
由于这里存在三种情况,接下来的讨论假设\(P\)位于\(AB\)线段上
对于\(P\),再细分为三种情况进行讨论:
① 假如\(P\)是\(AB\)中点
明显的,取\(C\)点为\(Q\)点即可平分三角形
② 如果\(P\)在\(AB\)线段上接近点\(A\)
那么\(Q\)点一定位于线段\(BC\)上
于是我们以\(B,C\)点作为二分的初始边界点\(L,R\),每次取边界点的中点\(M\)进行判断
由于\(AB\)与\(BC\)的公共点为\(B\),所以\(PBM\)便能够组成一个三角形,如上图所示
记\(\triangle PBM\)的面积为\(S_1\),\(\triangle ABC\)的面积为\(S\)
如果\(S_1\lt\frac S 2\),说明\(M\)点过于靠近\(B\),此时应当让左边界点\(L=M\)
如果\(S_2\gt \frac S 2\),说明\(M\)点过于靠近\(C\),此时应当让有边界点\(R=M\)
如此往复二分,直到精度允许即可作为答案
③ 如果\(P\)在\(AB\)线段上接近点\(B\)
那么\(Q\)点一定位于线段\(AC\)上,处理过程同上
代码
#include<bits/stdc++.h>
using namespace std;
const double eps=1e-12;
int dbcmp(double x){
if(fabs(x)<=eps)
return 0;
return (x<0?-1:1);
}
double Hypot(double x,double y){
return sqrt(x*x+y*y);
}
struct Point{
double x,y;
Point(){}
Point(double _x,double _y){
x=_x;
y=_y;
}
Point operator +(const Point &b)const{
return Point(x+b.x, y+b.y);
}
Point operator -(const Point &b)const{
return Point(x-b.x, y-b.y);
}
Point operator *(const double &k)const{
return Point(x*k, y*k);
}
Point operator /(const double &k)const{
return Point(x/k, y/k);
}
double operator *(const Point &b)const{
return x*b.x + y*b.y;
}
double operator ^(const Point &b)const{
return x*b.y - y*b.x;
}
double distance(Point p){
return Hypot(x-p.x, y-p.y);
}
}p[4];
struct Line{
Point s,e;
Line(){}
Line(Point _s,Point _e){
s=_s;
e=_e;
}
bool isPointOnSegment(Point p){
return dbcmp((p-s)^(e-s))==0&&dbcmp((p-s) * (p-e))<=0;
}
}ab,bc,ac;
// 叉积求三角形面积
double getArea(Point &a,Point &b,Point &c){
return fabs((b-a)^(c-a)/2.0);
}
// 求中点
Point getMidPoint(Point &a,Point &b) {
return (a+b)/2.0;
}
// 控制二分次数
const int iterationTime=100;
void solve()
{
for(int i=0;i<4;i++)
scanf("%lf%lf",&p[i].x,&p[i].y);
ab=Line(p[0],p[1]);
ac=Line(p[0],p[2]);
bc=Line(p[1],p[2]);
double S=getArea(p[0],p[1],p[2]),halfOfArea=S/2.0;
// 如果P点在线段AB上
if(ab.isPointOnSegment(p[3]))
{
double disa=p[0].distance(p[3]),disb=p[1].distance(p[3]);
if(disa>disb) //接近B
{
Point l=p[0],r=p[2];
for(int i=0;i<iterationTime;i++)
{
Point mid=getMidPoint(l,r);
if(dbcmp(getArea(mid,p[3],p[0])-halfOfArea)<0)
l=mid;
else if(dbcmp(getArea(mid,p[3],p[0])-halfOfArea)>0)
r=mid;
else
break;
}
Point mid=getMidPoint(l,r);
printf("%.12f %.12f\n",mid.x,mid.y);
}
else if(disa<disb) //接近A
{
Point l=p[1],r=p[2];
for(int i=0;i<iterationTime;i++)
{
Point mid=getMidPoint(l,r);
if(dbcmp(getArea(mid,p[3],p[1])-halfOfArea)<0)
l=mid;
else if(dbcmp(getArea(mid,p[3],p[1])-halfOfArea)>0)
r=mid;
else
break;
}
Point mid=getMidPoint(l,r);
printf("%.12f %.12f\n",mid.x,mid.y);
}
else //为AB中点,直接输出C
printf("%.12f %.12f\n",p[2].x,p[2].y);
}
// 如果P点在线段AC上
else if(ac.isPointOnSegment(p[3]))
{
double disa=p[0].distance(p[3]),disc=p[2].distance(p[3]);
if(disa>disc) //接近C
{
Point l=p[0],r=p[1];
for(int i=0;i<iterationTime;i++)
{
Point mid=getMidPoint(l,r);
if(dbcmp(getArea(mid,p[3],p[0])-halfOfArea)<0)
l=mid;
else if(dbcmp(getArea(mid,p[3],p[0])-halfOfArea)>0)
r=mid;
else
break;
}
Point mid=getMidPoint(l,r);
printf("%.12f %.12f\n",mid.x,mid.y);
}
else if(disa<disc) //接近A
{
Point l=p[0],r=p[2];
for(int i=0;i<iterationTime;i++)
{
Point mid=getMidPoint(l,r);
if(dbcmp(getArea(mid,p[3],p[2])-halfOfArea)<0)
r=mid;
else if(dbcmp(getArea(mid,p[3],p[2])-halfOfArea)>0)
l=mid;
else
break;
}
Point mid=getMidPoint(l,r);
printf("%.12f %.12f\n",mid.x,mid.y);
}
else //为AC中点,直接输出B
printf("%.12f %.12f\n",p[1].x,p[1].y);
}
// 如果P点在线段BC上
else if(bc.isPointOnSegment(p[3]))
{
double disb=p[1].distance(p[3]),disc=p[2].distance(p[3]);
if(disb>disc) //接近C
{
Point l=p[0],r=p[1];
for(int i=0;i<iterationTime;i++)
{
Point mid=getMidPoint(l,r);
if(dbcmp(getArea(mid,p[3],p[1])-halfOfArea)<0)
r=mid;
else if(dbcmp(getArea(mid,p[3],p[1])-halfOfArea)>0)
l=mid;
else
break;
}
Point mid=getMidPoint(l,r);
printf("%.12f %.12f\n",mid.x,mid.y);
}
else if(disb<disc) //接近B
{
Point l=p[0],r=p[2];
for(int i=0;i<iterationTime;i++)
{
Point mid=getMidPoint(l,r);
if(dbcmp(getArea(mid,p[3],p[2])-halfOfArea)<0)
r=mid;
else if(dbcmp(getArea(mid,p[3],p[2])-halfOfArea)>0)
l=mid;
else
break;
}
Point mid=getMidPoint(l,r);
printf("%.12f %.12f\n",mid.x,mid.y);
}
else //为BC中点,直接输出A
printf("%.12f %.12f\n",p[0].x,p[0].y);
}
else
puts("-1");
}
int main()
{
int T;
scanf("%d",&T);
while(T--)
solve();
return 0;
}
H - Pair Of Lines
来源
标签
叉积的运用(点与直线的关系)
题意
给定\(n\ (1\le n\le 10^5)\)个点,问是否存在两条直线,使得所有的点都在这两条直线上
思路
首先,如果\(n\le 4\),答案一定存在
否则,假设答案存在(存在两条直线\(L_1,L_2\)包含所有给定点)
我们任意取三个点,这三个点中一定会有两个点属于其中一条直线(假定为\(L_1\))
于是我们获得了一条直线\(L_1\),将所有在\(L_1\)线上的点全部忽略,考虑所有不在\(L_1\)上的点
如果答案存在,剩下的所有点也应当共线
如果剩下的点数\(\le 2\),答案一定存在;否则,任意再取两个点获得一条线,判断其余点是否都在这条直线上即可
代码
#include<bits/stdc++.h>
using namespace std;
typedef pair<int,int> P;
typedef long long ll;
const int INF=0x3f3f3f3f;
const double eps=1e-12;
const double PI=acos(-1.0);
int dbcmp(double x){
if(fabs(x)<eps)
return 0;
return x<0?-1:1;
}
struct Point{
double x,y;
Point(){}
Point(double _x,double _y){
x=_x;
y=_y;
}
Point operator +(const Point &b)const{
return Point(x+b.x,y+b.y);
}
Point operator -(const Point &b)const{
return Point(x-b.x,y-b.y);
}
double operator *(const Point &b)const{ //点积
return x*b.x+y*b.y;
}
double operator ^(const Point &b)const{ //叉积
return x*b.y-y*b.x;
}
};
struct Line{
Point s,e;
Line(){}
Line(Point _s,Point _e){
s=_s;
e=_e;
}
bool pointOnLine(Point p){ //判断点是否在直线上
return dbcmp((p-s)^(p-e))==0;
}
};
int n;
Point p[100010];
bool check(int a,int b)
{
Line L1=Line(p[a],p[b]);
//记录所有不在L1上的点的编号
vector<int> vec;
for(int i=1;i<=n;i++)
if(!L1.pointOnLine(p[i]))
vec.push_back(i);
int siz=vec.size();
if(siz<=2)
return true;
Line L2=Line(p[vec[0]],p[vec[1]]);
for(int i=2;i<siz;i++)
if(!L2.pointOnLine(p[vec[i]]))
return false;
return true;
}
int main()
{
scanf("%d",&n);
if(n<=4)
{
puts("YES");
return 0;
}
for(int i=1;i<=n;i++)
scanf("%lf%lf",&p[i].x,&p[i].y);
if(check(1,2)||check(2,3)||check(1,3))
puts("YES");
else
puts("NO");
return 0;
}
I - Everything Has Changed
来源
标签
圆的关系、余弦定理
题意
给定中点在原点\((0,0)\),半径为\(R\)的大圆,再给定\(m\)个小圆
题目保证小圆两两不相交,且小圆不会将大圆覆盖
小圆会覆盖在大圆的上面
问最终大圆外侧没有被覆盖的区域的周长是多少
思路
由于小圆两两不相交,所以每个小圆可以单独进行考虑
讨论大圆与任意一个小圆之间的关系:
① 两圆重合
- 本题不存在该情况,忽略
② 两圆外离
- 对答案无贡献,忽略
③ 两圆外切
- 并没有覆盖到大圆的区域,故对答案无贡献,忽略
④ 两圆相交
- 假设交于\(AB\)两点,则答案需要减去大圆上\(AB\)两点间圆弧的长度(被覆盖的部分),再加上小圆上\(AB\)两点间圆弧的长度(覆盖的部分)
- 发现两圆半径\(r_1,r_2\)与圆心距\(d\)都能直接求出,根据余弦定理便能求出两个圆心角的弧度,最后直接计算即可
⑤ 两圆内切
- 答案加上小圆的周长
⑥ 两圆内含
- 由于求的是外侧周长,故对答案无贡献,忽略
代码
#include<bits/stdc++.h>
using namespace std;
typedef pair<int,int> P;
typedef long long ll;
const int INF=0x3f3f3f3f;
const double eps=1e-12;
const double PI=acos(-1.0);
int dbcmp(double x){
if(fabs(x)<eps)
return 0;
return x<0?-1:1;
}
struct Point{
double x,y;
Point(){}
Point(double _x,double _y){
x=_x;
y=_y;
}
double distance(Point p){
return sqrt((x-p.x)*(x-p.x)+(y-p.y)*(y-p.y));
}
};
struct Circle{
Point p;
double r;
Circle(){}
Circle(Point _p,double _r){
p=_p;
r=_r;
}
double calPerimeter(){
return 2.0*PI*r;
}
};
void solve()
{
int m,R;
scanf("%d%d",&m,&R);
Circle big=Circle(Point(0,0),R);
double ans=big.calPerimeter();
for(int i=1;i<=m;i++)
{
double x,y,r;
scanf("%lf%lf%lf",&x,&y,&r);
Point p=Point(x,y);
Circle cir=Circle(p,r);
double d=p.distance(big.p);
if(dbcmp(abs(R-r)-d)<0&&dbcmp(d-(R+r))<0)
{ //相交
double deg1=2*acos((R*R+d*d-r*r)/(2.0*R*d));
double deg2=2*acos((r*r+d*d-R*R)/(2.0*r*d));
ans-=deg1*R;
ans+=deg2*r;
}
else if(dbcmp(abs(R-r)-d)==0)
{ //内切
ans+=cir.calPerimeter();
}
}
printf("%.20f\n",ans);
}
int main()
{
int T;
scanf("%d",&T);
while(T--)
solve();
return 0;
}
J - Wall
来源
标签
凸包
题意
给定\(N\ (N\le 1000)\)个点,尝试用一个二维图形将所有点全部覆盖在内,并且每个点到多边形的距离都\(\ge L\)
问这个二维图形的最小周长
思路
要覆盖所有的点,所以首先求出一个凸包
在凸包的边界点处,由于只存在一个点,并且要使得二维图形与其距离\(\ge L\)(明显\(=L\)是最优的)
所以在边界点处的图形应当是一个圆弧
对于整个凸包,所有圆弧拼凑起来应当形成一个圆
对于两个圆弧之间的距离,明显也就是凸包上这两个点的点间距
故答案为凸包长度\(+\pi L^2\)
代码
#include<cstdio>
#include<cmath>
#include<algorithm>
using namespace std;
const int INF=0x3f3f3f3f;
const double eps=1e-12;
const double PI=acos(-1.0);
int dbcmp(double x){
if(fabs(x)<eps)
return 0;
return x<0?-1:1;
}
struct Point{
double x,y;
Point(){}
Point(double _x,double _y){
x=_x;
y=_y;
}
Point operator +(const Point &b)const{
return Point(x+b.x,y+b.y);
}
Point operator -(const Point &b)const{
return Point(x-b.x,y-b.y);
}
Point operator *(const double &k)const{
return Point(x*k,y*k);
}
Point operator /(const double &k)const{
return Point(x/k,y/k);
}
};
typedef Point Vector;
inline double Cross(Vector a,Vector b){
return a.x*b.y-a.y*b.x;
}
inline double Distance(Point a,Point b){
return sqrt((a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y));
}
int n,top;
Point P[1010],result[1010];
bool cmp(Point A,Point B)
{
double ans=Cross(A-P[0],B-P[0]);
if(dbcmp(ans)==0)
return dbcmp(Distance(P[0],A)-Distance(P[0],B))<0;
return ans>0;
}
void Graham()
{
for(int i=1;i<n;i++)
if(P[i].y<P[0].y||(dbcmp(P[i].y-P[0].y)==0&&P[i].x<P[0].x))
swap(P[i],P[0]);
sort(P+1,P+n,cmp);
result[0]=P[0];
result[1]=P[1];
top=1;
for(int i=2;i<n;i++)
{
while(top>=1&&Cross(result[top]-result[top-1],P[i]-result[top-1])<0)
top--;
result[++top]=P[i];
}
}
int main()
{
int L;
scanf("%d%d",&n,&L);
for(int i=0;i<n;i++)
scanf("%lf%lf",&P[i].x,&P[i].y);
Graham();
double ans=PI*L*2;
for(int i=0;i<=top;i++)
ans+=Distance(result[i],result[(i+1)%(top+1)]);
printf("%.0f\n",ans);
return 0;
}
K - geometric problem
来源
题意
给出平面上六个点\(A,B,M,N,X,Y\)以及两条直线\(L_1,L_2\)
要求在直线\(L_1\)上选一点\(S\),并且该点位于四边形\(ABNM\)内;在直线\(L_2\)上选一点\(T\),并且该点位于四边形\(XYNM\)内
最终使得\(S_{\triangle ASB}=S_{\triangle SMTN}=S_{\triangle XYT}\)
试求出这两点的坐标
L - Cows
来源
标签
凸包、叉积的运用(求面积)
题意
给定\(n\ (1\le n\le 100000)\)棵树,要求以某些树作为顶点,两两之间以栅栏围起来,最终围成的区域用来养牛
已知一头牛想要至少\(50\)平方米的空间,问最多能够养几头牛
思路
求出凸包后计算面积,最后除以\(50\)向下取整即答案
代码
#include<cstdio>
#include<cmath>
#include<algorithm>
using namespace std;
const int INF=0x3f3f3f3f;
const double eps=1e-12;
const double PI=acos(-1.0);
int dbcmp(double x){
if(fabs(x)<eps)
return 0;
return x<0?-1:1;
}
struct Point{
double x,y;
Point(){}
Point(double _x,double _y){
x=_x;
y=_y;
}
Point operator +(const Point &b)const{
return Point(x+b.x,y+b.y);
}
Point operator -(const Point &b)const{
return Point(x-b.x,y-b.y);
}
Point operator *(const double &k)const{
return Point(x*k,y*k);
}
Point operator /(const double &k)const{
return Point(x/k,y/k);
}
};
typedef Point Vector;
inline double Cross(Vector a,Vector b){
return a.x*b.y-a.y*b.x;
}
inline double Distance(Point a,Point b){
return sqrt((a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y));
}
int n,top;
Point P[10010],result[10010];
bool cmp(Point A,Point B)
{
double ans=Cross(A-P[0],B-P[0]);
if(dbcmp(ans)==0)
return dbcmp(Distance(P[0],A)-Distance(P[0],B))<0;
return ans>0;
}
void Graham()
{
for(int i=1;i<n;i++)
if(P[i].y<P[0].y||(dbcmp(P[i].y-P[0].y)==0&&P[i].x<P[0].x))
swap(P[i],P[0]);
sort(P+1,P+n,cmp);
result[0]=P[0];
result[1]=P[1];
top=1;
for(int i=2;i<n;i++)
{
while(top>=1&&Cross(result[top]-result[top-1],P[i]-result[top-1])<0)
top--;
result[++top]=P[i];
}
}
int main()
{
scanf("%d",&n);
for(int i=0;i<n;i++)
scanf("%lf%lf",&P[i].x,&P[i].y);
Graham();
if(top<2)
{
printf("0\n");
return 0;
}
double s=0;
for(int i=1;i<top;i++)
{
Vector v1=result[i]-result[0];
Vector v2=result[i+1]-result[0];
s+=Cross(v1,v2);
}
s/=2;
if(s<0)
s=-s;
printf("%lld\n",(long long)(s/50));
return 0;
}
M - Scrambled Polygon
来源
标签
极角排序
题意
给定一个凸包上的点,但这些点的顺序被打乱了
已知点\((0,0)\)在凸包上,试从点\((0,0)\)开始逆时针输出这个凸包上的所有点
思路
极角排序输出即可
代码
#include<cstdio>
#include<cmath>
#include<algorithm>
using namespace std;
const int INF=0x3f3f3f3f;
const double eps=1e-12;
const double PI=acos(-1.0);
int dbcmp(double x){
if(fabs(x)<eps)
return 0;
return x<0?-1:1;
}
struct Point{
double x,y;
Point(){}
Point(double _x,double _y){
x=_x;
y=_y;
}
Point operator +(const Point &b)const{
return Point(x+b.x,y+b.y);
}
Point operator -(const Point &b)const{
return Point(x-b.x,y-b.y);
}
Point operator *(const double &k)const{
return Point(x*k,y*k);
}
Point operator /(const double &k)const{
return Point(x/k,y/k);
}
};
typedef Point Vector;
inline double Cross(Vector a,Vector b){
return a.x*b.y-a.y*b.x;
}
inline double Distance(Point a,Point b){
return sqrt((a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y));
}
int n;
Point P[55];
bool cmp(Point A,Point B)
{
double ans=Cross(A-P[0],B-P[0]);
if(dbcmp(ans)==0)
return dbcmp(Distance(P[0],A)-Distance(P[0],B))<0;
return ans>0;
}
int main()
{
while(scanf("%lf%lf",&P[n].x,&P[n].y)!=EOF)
n++;
sort(P+1,P+n,cmp);
for(int i=0;i<n;i++)
printf("(%.0f,%.0f)\n",P[i].x,P[i].y);
return 0;
}
N - Grandpa's Estate
来源
标签
凸包、叉积的运用(点与直线的关系)
题意
一个凸包农场是由一些钉子与一个绳子围成的,所有的钉子都只出现在凸包的边上
现在绳子和部分钉子丢了
问通过剩余的钉子能否判断农场是否变小了
如果可以确定农场并没有因为某些钉子的丢失而变小,输出\(YES\)
否则,输出\(NO\)
思路
由于原农场是一个凸包
假如剩下的钉子组成的凸包中,每一条边除了两端点,还有至少一个钉子存在
那么就能够说明这条边没有被改变(中间的钉子证明了这条边还是原来的凸包边)
否则,如果某条边仅由两端点组成
那么便无法确定原本的凸包中,这两个钉子之间是否还有其它钉子存在(可以在凸包外侧再放一个钉子,并且保证凸包性质不变,且所有钉子还是都在凸包边上)
所以做一遍凸包,再判断每条边的点数是否\(\ge 3\)即可
代码
#include<cstdio>
#include<cmath>
#include<algorithm>
using namespace std;
const int INF=0x3f3f3f3f;
const double eps=1e-12;
const double PI=acos(-1.0);
int dbcmp(double x){
if(fabs(x)<eps)
return 0;
return x<0?-1:1;
}
struct Point{
double x,y;
Point(){}
Point(double _x,double _y){
x=_x;
y=_y;
}
Point operator +(const Point &b)const{
return Point(x+b.x,y+b.y);
}
Point operator -(const Point &b)const{
return Point(x-b.x,y-b.y);
}
Point operator *(const double &k)const{
return Point(x*k,y*k);
}
Point operator /(const double &k)const{
return Point(x/k,y/k);
}
};
typedef Point Vector;
inline double Cross(Vector a,Vector b){
return a.x*b.y-a.y*b.x;
}
inline double Distance(Point a,Point b){
return sqrt((a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y));
}
int n,top;
Point P[1010],result[1010];
bool cmp(Point A,Point B)
{
double ans=Cross(A-P[0],B-P[0]);
if(dbcmp(ans)==0)
return dbcmp(Distance(P[0],A)-Distance(P[0],B))<0;
return ans>0;
}
void Graham()
{
for(int i=1;i<n;i++)
if(P[i].y<P[0].y||(dbcmp(P[i].y-P[0].y)==0&&P[i].x<P[0].x))
swap(P[i],P[0]);
sort(P+1,P+n,cmp);
result[0]=P[0];
result[1]=P[1];
top=1;
for(int i=2;i<n;i++)
{
while(top>=1&&Cross(result[top]-result[top-1],P[i]-result[top-1])<0)
top--;
result[++top]=P[i];
}
}
void solve()
{
scanf("%d",&n);
for(int i=0;i<n;i++)
scanf("%lf%lf",&P[i].x,&P[i].y);
//最少的可行情况是三角形,且每条边上都还有一个多余的点
if(n<6)
{
puts("NO");
return;
}
Graham();
for(int i=0;i<=top;i++)
{
//对于凸包上的每条边
Vector vec=result[i]-result[(i+1)%(top+1)];
int cnt=0;
//枚举所有点,判断是否在边上
for(int j=0;j<n;j++)
{
Vector vec2=result[i]-P[j];
if(dbcmp(Cross(vec,vec2))==0)
cnt++;
}
if(cnt<=2)
{
puts("NO");
return;
}
}
puts("YES");
}
int main()
{
int T;
scanf("%d",&T);
while(T--)
solve();
return 0;
}
O - Angel Beats
来源
标签
计算几何综合、数论
题意
在平面上给定\(n\ (2\le n\le 2000)\)个互不相同的点\(P_1,P_2,\cdots,P_n\)
有\(q\ (2\le q\le 2000)\)次询问,每次询问给定一个点\(Q\),问\(n\)个点中有多少种方案,选出两个点与点\(Q\)能够组成直角三角形
保证原有的点以及所有询问给定的点都两两不相同
思路
题目要求构造出直角三角形,每次询问的点要么就是直角点,要么不是,分两种情况讨论
① 如果询问的点是直角点
将从询问的点\(Q\)出发,到所有原有的点\(P_i\)的这\(n\)个向量求出
由于当前是从询问的点\(Q\)的视角看问题,所以我们可以将向量的\(x,y\)看作分数型,制定一个规则进行化简
代码中的规则是:先化至最简分数,如果\(x\)小于\(0\)则将负号转移至\(y\),如果\(x\)等于\(0\)则要求\(y\)必须是非负整数
然后借助\(map\)进行存储这\(n\)个向量
由于在平面直角坐标系中,向量\(\vec{u}=(x,y)\)如果想要逆时针旋转\(\theta\)度,则旋转后的向量\(\vec{u}'={x',y'}\)应当满足的条件是:
现在我们想获得与\(\vec{u}\)垂直的向量\(\vec{v}\),所以定\(\theta=90^{\circ}\),得到\(\vec{v}=(-y,x)\)
单位化后,在\(map\)中查询存在多少个\(\vec{v}\),即表示以\(\vec{u},\vec{v}\)作为直角边,直角点为\(P\)的直角三角形的数量
② 如果询问的点不是直角点
换个角度看,我们从原有的点\(P_i\)出发,假设\(P_i\)是当前讨论的直角三角形的直角点
将从\(P_i\)出发,到所有其他原有点\(P_j\ (j\neq i)\)的这\(n-1\)个向量求出
同样的看作分数型,按规则进行化简后存入\(map\)中
然后,考虑询问点\(Q\),定\(\vec{u}=\vec{P_iQ}\)
考虑所有与\(\vec{u}\)垂直的向量\(\vec{v}\)数量,这也就表示以\(\vec{u},\vec{v}\)作为直角边,点\(P_i\)作为直角点,并且包含点\(Q\)的直角三角形数量
上述两点在反复处理过程中存在冗余,故本题可以离线处理所有询问进行优化
代码
很卡常,建议能不多写尽量不多写;多组数据
#include<bits/stdc++.h>
using namespace std;
int gcd(int a,int b){
return !b?a:gcd(b,a%b);
}
struct Point{
int x,y;
Point(){}
Point(int _x,int _y){
x=_x;
y=_y;
}
// map排序用
bool operator < (const Point &p) const{
if(x!=p.x)return x<p.x;
return y<p.y;
}
bool operator == (const Point &p) const{
return x==p.x&&y==p.y;
}
Point operator +(const Point &b)const{
return Point(x+b.x,y+b.y);
}
Point operator -(const Point &b)const{
return Point(x-b.x,y-b.y);
}
};
// 单位化向量,便于判断相同向量
void unitization(Point &p)
{
int g=gcd(p.x,p.y);
p.x/=g,p.y/=g;
if(p.x<0)
p.x=-p.x,p.y=-p.y;
else if(p.x==0&&p.y<0)
p.y=-p.y;
}
int n,q;
Point pa[2010],pb[2010];
int ans[2010];
int main()
{
// 注意本题多组数据
while(scanf("%d%d",&n,&q)!=EOF)
{
for(int i=1;i<=n;i++)
scanf("%d%d",&pa[i].x,&pa[i].y);
for(int i=1;i<=q;i++)
scanf("%d%d",&pb[i].x,&pb[i].y),ans[i]=0;
// 如果询问点作为直角点
for(int i=1;i<=q;i++)
{
map<Point,int> mp;
for(int j=1;j<=n;j++)
{
// 从询问点pb i的视角看问题
Point pt=pb[i]-pa[j];
unitization(pt);
mp[pt]++;
pt=Point(-pt.y,pt.x);
unitization(pt);
if(mp.count(pt))
ans[i]+=mp[pt];
}
}
// 如果询问点不是直角点
for(int i=1;i<=n;i++)
{
map<Point,int> mp;
for(int j=1;j<=n;j++)
{
if(i==j)
continue;
// 从原有点pa i的视角看问题
Point pt=pa[i]-pa[j];
unitization(pt);
mp[pt]++;
}
for(int j=1;j<=q;j++)
{
Point pt=pa[i]-pb[j];
pt=Point(-pt.y,pt.x);
unitization(pt);
if(mp.count(pt))
ans[j]+=mp[pt];
}
}
for(int i=1;i<=q;i++)
printf("%d\n",ans[i]);
}
return 0;
}
P - A Star not a Tree?
来源
标签
模拟退火
题意
给定平面上\(n\ (n\le 100)\)个已知点,尝试在平面中找到一个点\(P\),使得\(P\)点与所有已知点的距离之和最小
求这个距离和的最小值,四舍五入为整数
思路
退火,每次以退火温度来限制随机范围,借助随机数确定一个随机方向,通过三角函数获得每次随机得到的新点
根据退火,如果新点比旧点更优,则直接将答案替换成新点;否则,给定一判定规则使其有概率替换成次优点
当退火温度逐渐降低,跳转的距离逐渐变短,次优点的跳转也将会变得不再频繁,最终便找到了最优近似解
本题数据很小,适合训练模拟退火,并不会过于卡温度及退火常量
代码
#include<cstdio>
#include<cmath>
#include<cstdlib>
using namespace std;
const double PI=acos(-1.0);
struct Point{
double x,y;
Point(){}
Point(double _x,double _y){
x=_x;
y=_y;
}
Point operator -(const Point &b)const{
return Point(x-b.x,y-b.y);
}
double operator ^(const Point &b)const{
return x*b.y-y*b.x;
}
double operator *(const Point &b)const{
return x*b.x+y*b.y;
}
Point operator +(const Point &b)const{
return Point(x+b.x,y+b.y);
}
Point operator *(const double &k)const{
return Point(x*k,y*k);
}
Point operator /(const double &k)const{
return Point(x/k,y/k);
}
double distance(Point p){
return sqrt((x-p.x)*(x-p.x)+(y-p.y)*(y-p.y));
}
};
double getRnd(){
return 1.0*rand()/RAND_MAX;
}
int n;
Point p[105];
double sumDistance(Point &pt)
{
double r=0;
for(int i=1;i<=n;i++)
r+=pt.distance(p[i]);
return r;
}
int main()
{
srand(114514);
Point pt=Point(0,0);
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
scanf("%lf%lf",&p[i].x,&p[i].y);
pt=pt+p[i];
}
pt=pt/n;
double cursum=sumDistance(pt);
double tem=100000,mis=0.997;
while(tem>1e-8)
{
double rad=2*PI*getRnd();
Point newpt=Point(pt.x+cos(rad),pt.y+sin(rad));
double newsum=sumDistance(newpt);
double val=cursum-newsum;
if(val>0||exp(val/tem)>getRnd())
pt=newpt,cursum=newsum;
tem*=mis;
}
printf("%d\n",int(cursum+0.5));
return 0;
}