小欧的最短路
[题目链接](https://www.nowcoder.com/practice/dcdac37856144dd58faf7e8c8450c8aa)
思路
小欧站在字符串的开头(位置 0),需要走到字符串的结尾(位置 )。每一步有两种走法:
- 相邻移动:从位置
走到位置
,代价为
(ASCII 码值之差的绝对值)。
- 同字母跳跃:从位置
跳到下一个与
相同的字母所在的位置
,代价为
。
求从位置 0 到位置 的最小总代价。
动态规划
定义 为从位置 0 到达位置
的最小代价,初始
,其余为正无穷。
对于每个位置 ,我们有两种转移:
- 相邻移动:
- 同字母跳跃:设
是位置
之后第一个满足
的位置,则
预处理 next_same 数组
为了快速找到每个位置之后下一个相同字母的位置,我们从后往前扫描,用一个大小为 26 的数组记录每个字母最近一次出现的位置。这样 就是位置
之后第一个与
相同的位置(若不存在则为
)。
关于跳跃代价的溢出处理
跳跃代价为 ,当距离较大时会溢出 64 位整数。但由于相邻移动每步代价最多为 25,走
步的代价上界为
,而跳跃代价
增长极快,当
时
已远超任何合理的路径代价,因此直接跳过即可。
样例演示
字符串 aba():
- 从位置 0:相邻移动到位置 1,代价
,
;跳到位置 2(下一个
a),代价,
- 从位置 1:相邻移动到位置 2,代价
,
答案 。
复杂度分析
- 时间复杂度:
,预处理 next\_same 数组和 DP 转移各遍历一次。
- 空间复杂度:
,存储
数组和
数组。
代码
#include <bits/stdc++.h>
using namespace std;
int main() {
int n;
scanf("%d", &n);
char s[200005];
scanf("%s", s);
vector<long long> dp(n, LLONG_MAX);
dp[0] = 0;
// 预处理每个位置之后下一个相同字母的位置
int nextSame[200005];
int last[26];
memset(last, -1, sizeof(last));
for (int i = n - 1; i >= 0; i--) {
int c = s[i] - 'a';
nextSame[i] = last[c];
last[c] = i;
}
for (int i = 0; i < n - 1; i++) {
if (dp[i] == LLONG_MAX) continue;
// 相邻移动
long long cost = dp[i] + abs(s[i] - s[i + 1]);
if (cost < dp[i + 1]) dp[i + 1] = cost;
// 同字母跳跃
int j = nextSame[i];
if (j != -1 && j - i - 1 < 60) {
long long jumpCost = 1LL << (j - i - 1);
if (dp[i] + jumpCost < dp[j])
dp[j] = dp[i] + jumpCost;
}
}
printf("%lld\n", dp[n - 1]);
return 0;
}
import java.util.*;
import java.io.*;
public class Main {
public static void main(String[] args) throws Exception {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
int n = Integer.parseInt(br.readLine().trim());
String s = br.readLine().trim();
long[] dp = new long[n];
Arrays.fill(dp, Long.MAX_VALUE);
dp[0] = 0;
// 预处理每个位置之后下一个相同字母的位置
int[] nextSame = new int[n];
int[] last = new int[26];
Arrays.fill(last, -1);
for (int i = n - 1; i >= 0; i--) {
int c = s.charAt(i) - 'a';
nextSame[i] = last[c];
last[c] = i;
}
for (int i = 0; i < n - 1; i++) {
if (dp[i] == Long.MAX_VALUE) continue;
// 相邻移动
long cost = dp[i] + Math.abs(s.charAt(i) - s.charAt(i + 1));
if (cost < dp[i + 1]) dp[i + 1] = cost;
// 同字母跳跃
int j = nextSame[i];
if (j != -1 && j - i - 1 < 60) {
long jumpCost = 1L << (j - i - 1);
if (dp[i] + jumpCost < dp[j])
dp[j] = dp[i] + jumpCost;
}
}
System.out.println(dp[n - 1]);
}
}

京公网安备 11010502036488号