题解:洛谷 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
【核心思想】
-
问题分析:给定两个长度为 \(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\)。
-
算法选择:
- 一维线性 DP(状态压缩):\(f[i][0/1]\) 表示构造到第 \(i\) 个位置,第 \(i\) 位选 \(a_i\)(状态 \(0\))或 \(b_i\)(状态 \(1\))时的最小代价
- 状态转移:当前选择只依赖于前一个位置的选择,四种转移取最小
-
关键步骤:
- 初始化:读取 \(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])\)
-
时间/空间复杂度:
- 时间复杂度:\(O(n)\),单层循环,每次 \(O(1)\) 转移
- 空间复杂度:\(O(n)\),\(f\) 二维数组(可滚动优化至 \(O(1)\))
-
线性 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
浙公网安备 33010602011771号