题目链接

空间跃迁

题目描述

个城市排成一列,从城市 到城市 的耗时为 。可以从 1 号城市出发到 号城市,途中可以使用一次空间跃迁。空间跃迁的半径为 ,即可以从第 个城市无时间消耗地传送到第 或第 个城市。求从 1 号城市到 号城市的最小总耗时。

解题思路

本题的目标是找到一条从城市 1 到城市 的路径,该路径包含一次空间跃迁,使得总耗时最小。

我们可以将整个行程分为三段:

  1. 从城市 1 走到城市
  2. 从城市 空间跃迁到城市 (其中 )。
  3. 从城市 走到城市

总耗时 = (从 1 到 的耗时) + (从 的耗时)。空间跃迁本身不耗时。

为了快速计算任意两城市间的行走耗时,我们可以使用前缀和。定义一个前缀和数组 ,其中 表示从城市 1 走到城市 的总耗时,即 。那么,从城市 走到城市 (假设 )的耗时就可以通过 计算得出。

我们的策略是遍历所有可能的跃迁点。假设我们从城市 进行空间跃迁,目的地是城市 。那么总耗时就是从城市 1 走到城市 的时间,加上从城市 走到城市 的时间。

我们需要枚举所有可能的出发城市 ),对于每个 ,计算两种可能的跃迁:

  1. 向前跃迁:跃迁到城市
    • 条件:
    • 耗时:(1走到) + (走到) =
  2. 向后跃迁:跃迁到城市
    • 条件:
    • 耗时:(1走到) + (走到) =
    • 注意,向后跃迁等价于从城市 出发,跃迁到城市 。因此,我们只需要考虑向前跃迁,即从一个城市 跃迁到 ,就可以覆盖所有情况,避免重复计算。

因此,我们的最终策略是:

  1. 计算出路径耗时的前缀和数组
  2. 不使用空间跃迁的总耗时为 ,将其作为初始的最小耗时。
  3. 遍历所有可能的跃迁出发点 (从 1 到 )。对于每个 ,计算从 跃迁到 的总耗时:
    • 总耗时 = (从 1 走到 的耗时) + (从 走到 的耗时)。
    • 这相当于省去了从 走到 的路程。
    • 节省的时间 =
    • 跃迁后的总耗时 = (不跃迁的总耗时) - (节省的时间) =
  4. 在所有可能的跃迁方案中取一个最小的总耗时,即为最终答案。

我们需要找到能节省最多时间的跃迁区间 。也就是找到一个 ,使得 最大。

代码

#include <iostream>
#include <vector>
#include <numeric>
#include <algorithm>

using namespace std;

int main() {
    ios::sync_with_stdio(false);
    cin.tie(NULL);

    int n;
    long long k;
    cin >> n >> k;

    // 前缀和数组,s[i] 表示从城市1到城市i+1的耗时
    vector<long long> s(n, 0);
    if (n > 1) {
        vector<long long> a(n - 1);
        for (int i = 0; i < n - 1; ++i) {
            cin >> a[i];
        }

        s[1] = a[0];
        for (int i = 1; i < n - 1; ++i) {
            s[i + 1] = s[i] + a[i];
        }
    }

    // 不使用空间跃迁的总耗时
    long long total_cost = s[n - 1];
    
    // 如果 k=0 或者 n <= k,无法进行有效的向前跃迁,只能走完全程
    if (k == 0 || n <= k) {
        cout << total_cost << "\n";
        return 0;
    }

    long long min_cost = total_cost;

    // 遍历所有可能的跃迁区间 [i, i+k]
    // i 是城市编号,从 1 到 n-k
    // 对应到前缀和数组下标是 i-1
    for (int i = 1; i <= n - k; ++i) {
        // 从城市 i 走到 i+k 的耗时
        long long saved_cost = s[i + k - 1] - s[i - 1];
        min_cost = min(min_cost, total_cost - saved_cost);
    }
    
    cout << min_cost << "\n";

    return 0;
}
import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int n = sc.nextInt();
        long k = sc.nextLong();
        
        // 前缀和数组,s[i] 表示从城市1到城市i+1的耗时
        long[] s = new long[n];
        if (n > 1) {
            long[] a = new long[n - 1];
            for (int i = 0; i < n - 1; i++) {
                a[i] = sc.nextLong();
            }

            s[1] = a[0];
            for (int i = 1; i < n - 1; i++) {
                s[i + 1] = s[i] + a[i];
            }
        }
        
        // 不使用空间跃迁的总耗时
        long totalCost = s[n - 1];
        
        // 如果 k=0 或者 n <= k,无法进行有效的向前跃迁,只能走完全程
        if (k == 0 || n <= k) {
            System.out.println(totalCost);
            return;
        }

        long minCost = totalCost;

        // 遍历所有可能的跃迁区间 [i, i+k]
        // i 是城市编号,从 1 到 n-k
        for (int i = 1; i <= n - k; i++) {
            // 从城市 i 走到 i+k 的耗时
            long savedCost = s[i + (int)k - 1] - s[i - 1];
            minCost = Math.min(minCost, totalCost - savedCost);
        }

        System.out.println(minCost);
    }
}
import sys

def solve():
    n, k = map(int, sys.stdin.readline().split())
    
    if n > 1:
        a = list(map(int, sys.stdin.readline().split()))
    else:
        a = []

    # 前缀和数组,s[i] 表示从城市1到城市i+1的耗时
    s = [0] * n
    if n > 1:
        s[1] = a[0]
        for i in range(1, n - 1):
            s[i + 1] = s[i] + a[i]
            
    # 不使用空间跃迁的总耗时
    total_cost = s[n - 1]
    
    # 如果 k=0 或者 n <= k,无法进行有效的向前跃迁,只能走完全程
    if k == 0 or n <= k:
        print(total_cost)
        return

    min_cost = total_cost

    # 遍历所有可能的跃迁区间 [i, i+k]
    # i 是城市编号,从 1 到 n-k
    for i in range(1, n - k + 1):
        # 从城市 i 走到 i+k 的耗时
        saved_cost = s[i + k - 1] - s[i - 1]
        min_cost = min(min_cost, total_cost - saved_cost)
        
    print(min_cost)

solve()

算法及复杂度

  • 算法:前缀和、枚举
  • 时间复杂度:计算前缀和数组需要 的时间,遍历所有可能的跃迁方案需要 的时间。因此,总时间复杂度为
  • 空间复杂度:需要一个数组来存储前缀和,因此空间复杂度为