求助 CCF 202009-4 星际旅行 python 未满分

本文解析了一道关于计算多点间最短路径的问题,特别是当路径受到特定几何形状(如圆)限制时的情况。文章详细讨论了不同情况下两点间距离的计算方法,并提供了一段Python代码作为示例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

此处贴出我的未满分代码,希望能有大佬帮忙指出错误,不胜感激涕零

题目叙述

详见参考博文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=0return 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 满分代码目录

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值