CCF 202009-4 星际旅行 python 未满分
此处贴出我的未满分代码,希望能有大佬帮忙指出错误,不胜感激涕零
题目叙述
详见参考博文2
问题描述:略
输入格式:略
输出格式:略
样例
样例输入1
2 3
2
3 1
5 1
1 3
3 -2
样例输出1
8.83711594354348
10.83711594354348
9.39104657990738
样例输入2
3 8
1
0 0 0
0 0 1
0 1 0
1 0 0
1 1 1
-1 -1 0
-1 0 -1
0 -1 -1
-1 -1 -1
样例输出2
14.12797001266400
14.12797001266400
14.12797001266400
17.90086240651788
13.95502966750398
13.95502966750398
13.95502966750398
14.99490548122857
未满分证明
只有45分,显示超时,就不贴出来了
解题思路
这题的核心在于计算两点距离。而计算两点距离分三种情况进行讨论,两点在圆上,两点不在圆上和一点在圆上一点不在圆上。下面对这三种情况进行讲解和分析,力图让大家都能看懂。实在不知道自己代码如何优化了,贴出来请大家帮忙([○・`Д´・ ○])。
一、两点均在圆上
求两点弧长即可:
通过求∠AOP1(直角三角形AOD,通过反正弦求∠AOD)从而求弧长
二、一点都在圆上,一点不在圆上
(一)小于等于切线距离
直接两点距离
(二)大于切线距离
切线(勾股定理,长边减半径)加圆上两点弧长距离
∠P1OP3(余弦定理)-∠P3OD(正弦定理)
三、两点均不在圆上
(一)两点连线与圆相切或者相离
1.正余弦法
求∠OAB的正弦值(先求其余弦值,1-余弦值)
2.等面积法
通过海伦-秦九韶公式求面积,然后求高
(二)两点连线与圆相交
1.本身相交
求两段切线(勾股定理)和一段弧长(∠P2OP3-∠P2OB-∠P3OC,均用余弦定理)
2.其延长线与圆相交
这个可能是大家会忽视的
判断∠ABO或者∠BAO为钝角(用余弦定理),结果为AB距离
未满分代码(自己的)
# 45,运行超时
import math
# 计算任意两点间距离
def ldjl(a, b):
sum = 0
for i in range(len(a)):
sum += (a[i] - b[i]) ** 2
return math.sqrt(sum)
# 计算两点都在圆上的距离,求弧度乘半径
def oncircle(a, b):
return math.asin(ldjl(a[:-1], b[:-1]) / 2 / r) * 2 * r
# 计算一点在圆上,一点不在圆上的距离
# 分两种情况 小于等于切线,直接两点距离
# 大于切线,切线加圆上两点距离
def one_no_circle(a, b, r):
l = max(a[-1], b[-1])
d = math.sqrt(l ** 2 - r ** 2)
ab = ldjl(a[:-1], b[:-1])
# 小于切线距离,两点间距离
# 大于切线距离,两点间距离加切点与另一点的弧长
if ab <= d:
return ab
else:
return (math.acos((r ** 2 + l ** 2 - ab ** 2) / (2 * r * l)) - math.asin(d / l)) * r + d
def o_l(a, b):
# 等面积法
oa = a[-1]
ob = b[-1]
ab = ldjl(b[:-1], a[:-1])
p = (oa + ob + ab) / 2
s = math.sqrt(p * (p - oa) * (p - ob) * (p - ab))
return s * 2 / ab
# # 利用正弦
# oa = ldjl(o, a)
# ob = ldjl(o, b)
# ab = ldjl(b, a)
# return oa * math.sqrt(1 - ((oa ** 2 + ab ** 2 - ob ** 2) / (2 * oa * ab)) ** 2)
# 两点都不在圆上
# 判断与圆的位置关系,相交还是相切还是相离开
def two_no_circle(a, b, r):
oa = a[-1]
ob = b[-1]
ab = ldjl(b[:-1], a[:-1])
d1 = math.sqrt(oa ** 2 - r ** 2)
d2 = math.sqrt(ob ** 2 - r ** 2)
# 计算圆心到线距离
ll = o_l(a, b)
cosAOB = (oa ** 2 + ob ** 2 - ab ** 2) / (2 * oa * ob)
# 如果相离或相切,直接计算两点距离,或者其延长线相交
if (ll >= r) or (oa ** 2 + ab ** 2 <= ob ** 2) or (ob ** 2 + ab ** 2 <= oa ** 2):
return ab
# 如果相交,直接计算两切线加一弧长
else:
return (math.acos(cosAOB) - math.acos(r / oa) - math.acos(r / ob)) * r + d1 + d2
# return 0
n, m = map(int, input().split())
r = float(input())
zb_r = list(map(int, input().split()))
p = []
for i in range(m):
tp = list(map(int, input().split()))
tp.append(ldjl(tp, zb_r))
p.append(tp)
dit = {}
for i in p:
sum = 0
for j in p:
# 两点相同
if i == j:
continue
if str(i) + str(j) in dit:
sum += dit[str(i) + str(j)]
else:
# 第一种情况,两点都在圆上
if (i[len(i) - 1] == r) and (j[len(j) - 1] == r):
temp = oncircle(i, j)
dit.setdefault(str(i) + str(j), temp)
dit.setdefault(str(j) + str(i), temp)
sum += temp
# 一点在圆上,一点不在圆上
elif (i[len(i) - 1] == r) or (j[len(j) - 1] == r):
temp = one_no_circle(i, j, r)
dit.setdefault(str(i) + str(j), temp)
dit.setdefault(str(j) + str(i), temp)
sum += temp
# 两点都不在圆上
else:
temp = two_no_circle(i, j, r)
dit.setdefault(str(i) + str(j), temp)
dit.setdefault(str(j) + str(i), temp)
sum += temp
print("%.14f" % sum)
满分代码(大佬的,C++版本)
#include<iostream>
#include<math.h>
#include<vector>
using namespace std;
long long m, n, r;
vector<long long> O;//存储黑洞中心
vector<long long> p[2000];//存储旅行点
long double dis[2000][2000];//两点间的旅行距离
long long dirdis2[2000][2000];//两点间直线距离的平方值
long long pr2[2000];//点到黑洞中心距离的平方值
//计算两点间直线距离的平方值(整型)
long long caldirdis2(int x, int y) {
long long ans=0;
for (int i = 0;i < n;i++) {
ans += (p[x].at(i) - p[y].at(i))*(p[x].at(i) - p[y].at(i));
}
return ans;
}
//判断连线是否在黑洞外
bool isOut(int x,int y) {
long double a,b,c,d,h;
long long pm1=0,pm2=0;
//根据公式计算O点到xy直线的距离h
a = sqrt(pr2[x]);
b = sqrt(pr2[y]);
c = sqrt(dirdis2[x][y]);
d = (a + b + c) / 2;
h = 2 * sqrt(d * (d - a) * (d - b) * (d - c)) / c;
//pm1为ox点乘yx;pm2为oy点乘yx;
for(int i=0;i<n;i++){
pm1+=p[x].at(i)*(p[x].at(i)-p[y].at(i));
pm2+=p[y].at(i)*(p[y].at(i)-p[x].at(i));
}
//若h大于r或者pm1或pm2小于等于零,则连线在黑洞外;
return (h >= r+0.000001||pm1<=0||pm2<=0);
}
//计算x,y两点与o点形成夹角的弧度值
long double caltheta(int x, int y) {
int mult=0;
long double theta;
//求ox点乘oy的值(整型)
for(int i=0;i<n;i++){
mult+=p[x].at(i)*p[y].at(i);
}
if(mult*mult == pr2[x] * pr2[y]){//判断cos值是否为1或-1,避免sqrt精度问题导致的nan值结果
if (mult>0){//cos值为1,theta=0;
return 0;
}else{//cos值为-1,theta=pi ;
return 3.1415926535897932;
}
}else{//其余情况根据公式求theta值
theta=acos(mult / sqrt(pr2[x] * pr2[y]));
return theta;
}
}
//两点间带曲线路程计算
long double calcirdis(int x, int y) {
long double a = pr2[x], b = pr2[y], c = dirdis2[x][y],ans;
/**分为三部分,黑洞表面曲线部分,x点到切点的距离,y点到切点的距离
黑洞表面曲线求取略微复杂,先调用caltheta(x, y)求<XOY>的弧度thete0
再用 acos(sqrt(r*r / a))求出<xo切点> 的弧度 thete1
同理 acos(sqrt(r*r / b))求出<yo切点> 的弧度 thete2
(thete0-thete1-thete2)*半径,则得出黑洞表面曲线的长度
**/
ans=caltheta(x, y)*r - acos(sqrt(r*r / a))*r - acos(sqrt(r*r / b))* r + sqrt(a - r * r) + sqrt(b - r * r);
return ans;
}
//计算x点与y点之间的旅行距离,并将该距离赋值给dis[X][Y]和dis[Y][X]
void caldis(int x, int y) {
dirdis2[x][y] = caldirdis2(x, y);//先计算两点间直线距离的平方值
dirdis2[y][x] = dirdis2[x][y];
if (isOut(x,y)) {//判定两点间连线是否在黑洞之外
dis[x][y]=sqrt(dirdis2[x][y]);//若在黑洞之外则路程为dirdis2开平方
dis[y][x]=dis[x][y];
}else {
dis[x][y] = calcirdis(x, y);//若在黑洞内则调用曲线路程函数计算
dis[y][x] = dis[x][y];
}
}
int main() {
int in;
long double ans;
//n维,m个点,黑洞半径为r;
cin >> n >> m >> r;
//黑洞中心的坐标值
for (int i = 0;i < n;i++) {
cin >> in;
O.push_back(in);
}
//输入m个n维的点
for (int i = 0;i < m;i++) {
pr2[i] = 0;
for (int j = 0;j < n;j++) {
cin >> in;
p[i].push_back(in - O.at(j));//记录坐标值为相对于黑洞中心位置的坐标值
pr2[i] += (in - O.at(j))*(in - O.at(j));//记录该点到黑洞中心距离的平方值(整型)
}
}
//调用caldis函数对dis数组赋值
for (int i = 0;i < m;i++) {
for (int j = i + 1;j < m;j++) {
caldis(i, j);
}
}
//输出结果
for (int i = 0;i < m;i++) {
ans = 0;
for (int j = 0;j < m;j++) {
ans = ans + dis[i][j];
}
printf("%.14Lf\n",ans);
}
return 0;
}
感谢及参考博文
部分内容参考以下链接,这里表示感谢 Thanks♪(・ω・)ノ
参考博文1 第二十次csp认证 第四题 星际旅行题解
https://blog.youkuaiyun.com/qq_43400598/article/details/108943029
参考博文2 第 20 次 CSP认证 202009-4 星际旅行(题目)
https://blog.youkuaiyun.com/weixin_45884316/article/details/108572543
需者自取传送门(∩ᄑ_ᄑ)⊃━☆【CCF 2013-2021】本博主整理历年至少前两题 python 满分代码目录