题解:洛谷 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

【核心思想】

  1. 问题分析:给定初始排列 \(a[1..N]\) 和目标排列 \(b[1..N]\),每次操作可选择一头奶牛向左移动若干位置。求将 \(a\) 变为 \(b\)最少操作次数。关键观察:向左移动不会改变奶牛之间的相对顺序(未被移动的奶牛保持原有相对顺序),因此问题转化为寻找最长保持相对顺序的子序列

  2. 算法选择

    • 映射 + 最长递增子序列(LIS)思想:将目标排列中每个值的位置映射出来,然后在初始排列中检查这些位置的相对顺序,统计"逆序"数量即为需要移动的次数
  3. 关键步骤

    • 位置映射:读取目标排列 \(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}\)
  4. 时间/空间复杂度

    • 时间复杂度:\(O(N)\),线性扫描
    • 空间复杂度:\(O(N)\),存储排列和映射
  5. 相对顺序保持 + 逆序统计的核心思想

    • 向左移动的特性:向左移动一头奶牛时,它只会越过右侧的奶牛,不会改变左侧未被移动奶牛的相对顺序。因此,若一组奶牛在初始和目标中的相对顺序一致,它们可以全部不被移动
    • 最长不动子序列:需要移动的奶牛数 = \(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
posted @ 2026-08-29 09:40  团爸讲算法  阅读(6)  评论(0)    收藏  举报