《算法竞赛·快冲300题》每日一题:“圆内的最短距离”

算法竞赛·快冲300题》将于2024年出版,是《算法竞赛》的辅助练习册。
所有题目放在自建的OJ New Online Judge
用C/C++、Java、Python三种语言给出代码,以中低档题为主,适合入门、进阶。


圆内的最短距离” ,链接: http://oj.ecustacm.cn/problem.php?id=1789

题目描述

【题目描述】 小蓝最开始在一个半径为1的圆心上,圆上有m个等分点,每个点有对应的一个颜色编号。
  小蓝需要走一条给定的颜色路径,也就是每次需要到达具有该颜色的任意一个点上。
  如下图所示,图上有5个点,按照顺时针颜色编号依次为1 4 2 2 3。
  小蓝需要走的颜色路径是1 2 4 1 3 2,由于颜色2有两个点,但是最短路径如图所示。
  现在给你上述信息,求小蓝走的最短距离。
在这里插入图片描述

【输入格式】 第一行为n,表示小蓝需要完成的颜色路径的长度。
  第二行为n个数字表示给定的颜色路径。
  第三行为m,表示圆上等分点的数量。
  第四行为按照顺时针给定的m个点的颜色编号。所有数字不超过100。
【输出格式】 输出的答案与标准答案绝对误差在10^-6以内视为正确。
【输入样例】

样例16
1 2 4 1 3 2
5
1 4 2 2 3

样例25
4 2 1 3 1
6
1 2 1 3 1 4

【输出样例】

样例17.604395

样例25.732051

题解

   首先计算第x点和第y点之间的距离。原点 O O O,直线 O O Ox和 O y Oy Oy的夹角等于 α = x − y m × 2 π \alpha=\frac {x-y}{m}\times2\pi α=mxy×2π,当半径等于1时,线段xy的长度等于 2 − 2 c o s α \sqrt{2-2cos\alpha} 22cosα
在这里插入图片描述
   把本题建模为图问题。图有点和边,点是圆周上的点,边是两点间的边。把样例1画成有向图:
在这里插入图片描述

   样例1的路径要求是“1 2 4 1 3 2”,起点是颜色“1”,下一步走到颜色“2”,颜色“2”在圆周上有2个点,所以上图画了2条边。
   显然,这是一个经典的动态规划问题:多段图最短路。
   定义状态dp[][]。dp[i][j]表示走到第i步,位于j点,且j点的颜色是路径要求的颜色,dp[i][j]是从起点到第j点的最短路径长度。
   最后得到的dp[n][j],是走完了n步,位于j点,且j点的颜色是路径第n步要求的颜色。可能有多个j点的颜色满足要求,在所有的dp[n][j]中,最小的那个就是答案。
   状态转移方程:从第i-1步到第i步,按要求的颜色找下一个点,并计算这个点的最短路。
   代码第23行计算dp,有三个for循环,计算复杂度为 O ( n m 2 ) O(nm^2) O(nm2)
【重点】 多段图最短路。

C++代码

#include <bits/stdc++.h>
using namespace std;
const double INF = 10000.0;
int n,m;
int p[110],c[110];                             //p[]: 要求走的路径;c[]:圆周上点的颜色
double dp[110][110];                           //第i步走到p[i]=c[j]点的最短路径
const double pi = acos(-1.0);                  //圆周率
double dis(int x, int y){                      //x和y点之间的距离
    double a = 2.0*abs(x-y)*pi/m;              //夹角a=2*pi*(x-y)/m
    return  sqrt(2-2*cos(a));
}
int main(){
    cin >> n;
    for(int i = 1; i <= n; i++) cin >> p[i];  //输入要求的路径编号
    cin >> m;
    for(int j = 1; j <= m; j++) cin >> c[j];  //输入圆周上的颜色编号
    for(int i = 1; i <= n; i++)
        for(int j = 1; j <= m; j++)
            dp[i][j] = INF;                   //初始化为无穷大
    //memset(dp,0x3f,sizeof(dp));             //这样初始化是错的,因为dp是double,不是整型
    for(int j = 1; j <= m; j++)
        if(p[1]==c[j])
            dp[1][j]=1.0;                     //圆心到第一个路径点的距离是1
    for(int i = 2; i <= n; i++)
        for(int j = 1; j <= m; j++)
            if(p[i]==c[j])                    //第i步要求的路径点 = j点的颜色编号
                for(int k = 1; k <= m; k++)
                    if(p[i-1]==c[k])          //上一步也应该符合颜色要求
                        dp[i][j] = min(dp[i][j],dp[i-1][k]+dis(k,j));       //状态转移方程
    double ans = INF;
    for(int j = 1; j <= m; j++)               //dp[n][]:第n步走到了要求的p[n]=c[j]点
        ans = min(ans, dp[n][j]);             //在所有符合要求的路径中找最短的
    printf("%.6f",ans);
    return 0;
}

Java代码

import java.util.Scanner;

public class Main {
    static final double INF = 10000.0;
    static int n, m;
    static int[] p = new int[110];
    static int[] c = new int[110];
    static double[][] dp = new double[110][110];

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        n = sc.nextInt();
        for (int i = 1; i <= n; i++)   p[i] = sc.nextInt();
        m = sc.nextInt();
        for (int j = 1; j <= m; j++)  c[j] = sc.nextInt();
        for (int i = 1; i <= n; i++) 
            for (int j = 1; j <= m; j++) 
                dp[i][j] = INF;
        for (int j = 1; j <= m; j++) 
            if (p[1] == c[j]) 
                dp[1][j] = 1.0;
        for (int i = 2; i <= n; i++) 
            for (int j = 1; j <= m; j++) 
                if (p[i] == c[j]) 
                    for (int k = 1; k <= m; k++) 
                        if (p[i - 1] == c[k]) 
                            dp[i][j] = Math.min(dp[i][j], dp[i - 1][k] + dis(k, j));
        double ans = INF;
        for (int j = 1; j <= m; j++) 
            ans = Math.min(ans, dp[n][j]);
        System.out.printf("%.6f", ans);
    }
    static double dis(int x, int y) {
        double a = 2.0 * Math.abs(x - y) * Math.acos(-1.0) / m;
        return Math.sqrt(2 - 2 * Math.cos(a));
    }
}

Python代码

import math
INF = 10000.0
n = int(input())
p =[0]+ list(map(int, input().split()))
m = int(input())
c =[0]+ list(map(int, input().split()))
dp = [[INF] * 110 for _ in range(110)]
def dis(x, y):
    a = 2.0 * abs(x - y) * math.acos(-1) / m
    return math.sqrt(2 - 2 * math.cos(a))
for j in range(1, m+1):
    if p[1] == c[j]:  dp[1][j] = 1.0
for i in range(2, n+1):
    for j in range(1, m+1):
        if p[i] == c[j]:
            for k in range(1, m+1):
                if p[i-1] == c[k]:
                    dp[i][j] = min(dp[i][j], dp[i-1][k] + dis(k, j))
ans = INF
for j in range(1, m+1):   ans = min(ans, dp[n][j])
print("%.6f" % ans)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

罗勇军

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值