题解:洛谷 B4163 序列选择

【题目来源】

洛谷:B4163 [BCSP-X 2024 12 月初中组] 序列选择

【题目描述】

给定两个长度为 \(n\) 的序列 \(a, b\),找出一个长为 \(n\) 的序列 \(c\),满足对于 \(i = 1, 2, \cdots, n\),有 \(c_i = a_i\)\(c_i = b_i\),使得 \(\sum_{i=2}^{n} |c_i - c_{i-1}|\) 最小,你只需要输出这个最小值。

【输入】

  • 输入的第一行包含一个正整数 \(n\)
  • 接下来一行 \(n\) 个整数,表示序列 \(a_i\)
  • 接下来一行 \(n\) 个整数,表示序列 \(b_i\)

【输出】

输出一行一个整数,表示 \(\sum_{i=2}^{n} |c_i - c_{i-1}|\) 的最小值。

【输入样例】

5
1 3 4 2 5
2 5 4 2 1

【输出样例】

5

【核心思想】

  1. 问题分析:给定两个长度为 \(n\) 的序列 \(a\)\(b\),构造序列 \(c\) 使得每个位置 \(c_i \in \{a_i, b_i\}\),最小化相邻元素差的绝对值之和 \(\sum_{i=2}^{n} |c_i - c_{i-1}|\)。这是一个线性 DP 问题,核心在于每个位置只有两种选择,状态只需记录当前位置选了 \(a_i\) 还是 \(b_i\)

  2. 算法选择

    • 一维线性 DP(状态压缩)\(f[i][0/1]\) 表示构造到第 \(i\) 个位置,第 \(i\) 位选 \(a_i\)(状态 \(0\))或 \(b_i\)(状态 \(1\))时的最小代价
    • 状态转移:当前选择只依赖于前一个位置的选择,四种转移取最小
  3. 关键步骤

    • 初始化:读取 \(n\)\(a[1..n]\)\(b[1..n]\)
    • 边界条件\(f[1][0] = 0\)(第 \(1\) 位选 \(a_1\)),\(f[1][1] = 0\)(第 \(1\) 位选 \(b_1\)),无前置差值
    • DP 递推\(i\)\(2\)\(n\)):
      • \(a_i\)\(f[i][0] = \min(f[i-1][0] + |a_i - a_{i-1}|, f[i-1][1] + |a_i - b_{i-1}|)\)
      • \(b_i\)\(f[i][1] = \min(f[i-1][0] + |b_i - a_{i-1}|, f[i-1][1] + |b_i - b_{i-1}|)\)
    • 输出答案\(\min(f[n][0], f[n][1])\)
  4. 时间/空间复杂度

    • 时间复杂度:\(O(n)\),单层循环,每次 \(O(1)\) 转移
    • 空间复杂度:\(O(n)\)\(f\) 二维数组(可滚动优化至 \(O(1)\)
  5. 线性 DP 的核心思想

    • 状态设计精简:每个位置只有两种选择,用 \(0/1\) 表示,将指数级枚举降为线性 DP
    • 局部最优子结构:第 \(i\) 位的最小代价仅依赖于第 \(i-1\) 位的两种状态,满足无后效性
    • 四种转移全覆盖\(a_i \to a_i\)\(b_i \to a_i\)\(a_i \to b_i\)\(b_i \to b_i\) 全部考虑,取最小保证全局最优
    • 边界无代价:第 \(1\) 位无前置元素,两种选择的初始代价均为 \(0\)
    • 适用于二选一序列构造、相邻代价最小化、路径选择类 DP 问题

【算法标签】

普及 #线性DP-一维

【代码详解】

#include <bits/stdc++.h>
using namespace std;
#define int long long // 将int定义为long long,防止求和时溢出
const int N = 200005; // 最大序列长度
int n, a[N], b[N]; // n:序列长度, a[i]/b[i]:第i个位置的两个可选值
int f[N][2]; // f[i][0/1]:构造到第i个位置,第i位选a[i](0)或b[i](1)时的最小代价

signed main()
{
    cin >> n; // 读入序列长度
    for (int i=1; i<=n; i++) // 读入序列a
        cin >> a[i];
    for (int i=1; i<=n; i++) // 读入序列b
        cin >> b[i];

    // 初始化DP数组为无穷大
    memset(f, 0x3f, sizeof(f));
    // 边界条件:第1个位置选a[1]或b[1]都没有前驱差值,代价为0
    f[1][0] = 0; // 第1位选a[1]
    f[1][1] = 0; // 第1位选b[1]

    // 动态规划:从第2位开始递推
    for (int i=2; i<=n; i++)
    {
        // 状态转移:第i位选a[i]时的最小代价
        // 可以从第i-1位选a[i-1]转移过来:代价增加|a[i]-a[i-1]|
        // 或从第i-1位选b[i-1]转移过来:代价增加|a[i]-b[i-1]|
        f[i][0] = min(f[i-1][0] + abs(a[i]-a[i-1]), f[i-1][1] + abs(a[i]-b[i-1]));

        // 状态转移:第i位选b[i]时的最小代价
        // 可以从第i-1位选a[i-1]转移过来:代价增加|b[i]-a[i-1]|
        // 或从第i-1位选b[i-1]转移过来:代价增加|b[i]-b[i-1]|
        f[i][1] = min(f[i-1][0] + abs(b[i]-a[i-1]), f[i-1][1] + abs(b[i]-b[i-1]));
    }
    // 答案为第n位选a[n]或b[n]中的较小值
    cout << min(f[n][0], f[n][1]) << endl;
    return 0;
}

【运行结果】

5
1 3 4 2 5
2 5 4 2 1
5
posted @ 2026-08-11 11:26  团爸讲算法  阅读(11)  评论(0)    收藏  举报