《算法竞赛·快冲300题》将于2024年出版,是《算法竞赛》的辅助练习册。
所有题目放在自建的OJ New Online Judge。
用C/C++、Java、Python三种语言给出代码,以中低档题为主,适合入门、进阶。
【题目描述】 小蓝最开始在一个半径为1的圆心上,圆上有m个等分点,每个点有对应的一个颜色编号。
小蓝需要走一条给定的颜色路径,也就是每次需要到达具有该颜色的任意一个点上。
如下图所示,图上有5个点,按照顺时针颜色编号依次为1 4 2 2 3。
小蓝需要走的颜色路径是1 2 4 1 3 2,由于颜色2有两个点,但是最短路径如图所示。
现在给你上述信息,求小蓝走的最短距离。
【输入格式】 第一行为n,表示小蓝需要完成的颜色路径的长度。
第二行为n个数字表示给定的颜色路径。
第三行为m,表示圆上等分点的数量。
第四行为按照顺时针给定的m个点的颜色编号。所有数字不超过100。
【输出格式】 输出的答案与标准答案绝对误差在10^-6以内视为正确。
【输入样例】
样例1:
6
1 2 4 1 3 2
5
1 4 2 2 3
样例2:
5
4 2 1 3 1
6
1 2 1 3 1 4
【输出样例】
样例1:
7.604395
样例2:
5.732051
首先计算第x点和第y点之间的距离。原点 O O O,直线 O O Ox和 O y Oy Oy的夹角等于 α = x − y m × 2 π \alpha=\frac {x-y}{m}\times2\pi α=mx−y×2π,当半径等于1时,线段xy的长度等于 2 − 2 c o s α \sqrt{2-2cos\alpha} 2−2cosα。
把本题建模为图问题。图有点和边,点是圆周上的点,边是两点间的边。把样例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)。
【重点】 多段图最短路。
#include
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;
}
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));
}
}
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)