题解:洛谷 P8184 Photoshoot 2
【题目来源】
洛谷:P8184 [USACO22FEB] Photoshoot 2 B - 洛谷
【题目描述】
在一个熟悉的情景中,Farmer John 正在为他的 \(N\) 头奶牛(\(1 \leq N \leq 10^5\),编号为 \(1 \cdots N\))排队拍照。
初始时,奶牛从左到右的排列顺序为 \(a_1, a_2, \cdots , a_N\)。Farmer John 的目标是将奶牛从左到右排列成 \(b_1, \cdots , b_N\) 的顺序。为了实现这一目标,他可以对排列顺序进行一系列修改。每次修改包括选择一头奶牛并将其向左移动若干位置。
请计算 Farmer John 将奶牛排列成目标顺序所需的最少修改次数。
【输入】
输入的第一行包含 \(N\)。第二行包含 \(a_1, a_2, \cdots , a_N\)。第三行包含 \(b_1, \cdots , b_N\)。
【输出】
输出将奶牛排列成目标顺序所需的最少修改次数。
【输入样例】
5
5 1 3 2 4
4 5 2 1 3
【输出样例】
2
【核心思想】
-
问题分析:给定初始排列 \(a[1..N]\) 和目标排列 \(b[1..N]\),每次操作可选择一头奶牛向左移动若干位置。求将 \(a\) 变为 \(b\) 的最少操作次数。关键观察:向左移动不会改变奶牛之间的相对顺序(未被移动的奶牛保持原有相对顺序),因此问题转化为寻找最长保持相对顺序的子序列。
-
算法选择:
- 映射 + 最长递增子序列(LIS)思想:将目标排列中每个值的位置映射出来,然后在初始排列中检查这些位置的相对顺序,统计"逆序"数量即为需要移动的次数
-
关键步骤:
- 位置映射:读取目标排列 \(b\),建立映射 \(b[\text{val}] = i\)(值 \(\text{val}\) 在目标排列中的位置为 \(i\))
- 转换初始排列:将初始排列 \(a\) 中的每个值替换为其在目标排列中的位置,得到序列 \(b[a[1]], b[a[2]], \dots, b[a[N]]\)
- 贪心统计逆序:
- 初始化 \(\text{pre} = b[a[1]]\)(第一个元素在目标中的位置)
- 遍历 \(i\) 从 \(2\) 到 \(N\):
- 若 \(b[a[i]] > \text{pre}\):说明该元素在目标中位于前一个元素之后,相对顺序正确,更新 \(\text{pre} = b[a[i]]\)
- 否则:相对顺序错误,该元素需要被移动,\(\text{ans}++\)
- 输出答案:\(\text{ans}\)
-
时间/空间复杂度:
- 时间复杂度:\(O(N)\),线性扫描
- 空间复杂度:\(O(N)\),存储排列和映射
-
相对顺序保持 + 逆序统计的核心思想:
- 向左移动的特性:向左移动一头奶牛时,它只会越过右侧的奶牛,不会改变左侧未被移动奶牛的相对顺序。因此,若一组奶牛在初始和目标中的相对顺序一致,它们可以全部不被移动
- 最长不动子序列:需要移动的奶牛数 = \(N\) - 最长保持相对顺序的子序列长度。代码中的贪心统计实际上等价于寻找最长递增子序列的长度
- 位置映射转化:将问题从"值匹配"转化为"位置比较",通过映射数组 \(b[\text{val}]\) 建立目标位置索引,简化比较逻辑
- 贪心正确性:维护当前最靠右的正确位置 \(\text{pre}\),只有比 \(\text{pre}\) 更靠右的元素才能延续正确顺序,否则必须移动
- 适用于排列变换、相对顺序保持、最小移动次数类问题
【解题思路】

【算法标签】
普及- #模拟
【代码详解】
#include <bits/stdc++.h>
using namespace std;
int n, ans=0;
int a[100005], b[100005];
int main()
{
cin >> n; // 输入n
for (int i=1; i<=n; i++) { // for循环遍历n个数
cin >> a[i]; // 记录n个数
}
for (int i=1; i<=n; i++) { // for循环遍历n个数
int tmp;
cin >> tmp; // 记录n个数的下标
b[tmp] = i;
}
int pre = b[a[1]]; // 定义起始位置为a数组中第1个数的位置
for (int i=2; i<=n; i++) { // 依次遍历后续n-1个数
if (b[a[i]]> pre) { // 如果当前这个数的位置比前一个数大
pre = b[a[i]]; // 说明相对位置没有变化,更新下一次要比较的位置
} else { // 否则说明相对位置发生了改变
ans++; // 需要挪一次
}
}
cout << ans << endl; // 返回挪的次数
return 0;
}
【运行结果】
5
5 1 3 2 4
4 5 2 1 3
2
浙公网安备 33010602011771号