携程0510笔试真题【单数组交换】

题目内容

游游有两个长度同为 nnn 的整数数组 aaa 和 bbb。她会对数组 aaa 执行标准冒泡排序来将其排成非递减序列(数组 bbb 始终不变):

  • 对于 i=1,2,...,n−1i = 1,2,\dots,n-1i=1,2,...,n−1,依次执行一趟扫描;
  • 在第 iii 趟中,按顺序检查 j=1,2,...,n−ij = 1,2,\dots,n-ij=1,2,...,n−i;
  • 若当前aj>aj+1a_j > a_{j+1}aj>aj+1,则交换这两个元素;否则不交换。
    每次实际发生交换时,其代价由当前位置对应的固定值 bj,bj+1b_j,b_{j+1}bj,bj+1 决定:
  • 若 bj≥bj+1b_j \ge b_{j+1}bj≥bj+1,则这次交换代价为 000;
  • 若 bj<bj+1b_j < b_{j+1}bj<bj+1,则这次交换代价为 111。
    请你计算,按照上述固定的冒泡排序过程把数组 aaa 排成非递减后,所有交换操作的总代价。

输入描述

每个测试文件均包含多组测试数据。第一行输入一个整数 T(1≤T≤105)T(1 \le T \le 10^5)T(1≤T≤105)代表数据组数。每组测试数据描述如下:

  • 第一行输入一个整数 n(1≤n≤2×105)n(1 \le n \le 2 \times 10^5)n(1≤n≤2×105)。
  • 第二行输入 nnn 个整数a1,a2,...,an(∣ai∣≤109)a_1,a_2,\dots,a_n(|a_i| \le 10^9)a1,a2,...,an(∣ai∣≤109)。
  • 第三行输入 nnn 个整数 b1,b2,...,bn(∣bi∣≤109)b_1,b_2,\dots,b_n(|b_i| \le 10^9)b1,b2,...,bn(∣bi∣≤109)。
    保证单个测试文件中所有测试数据的nnn之和不超过 5×1055 \times 10^55×105。

输出描述

对于每组测试数据,输出一行一个整数,表示按题目所述的固定冒泡排序过程将数组 aaa 排成非递减序列时,所有实际发生交换的总代价之和。。

样例1

输入

复制代码
2
4
2 1 2 1
3 1 2 2
6
17 15 12 10 18 3
1 5 1 3 1 2

输出

复制代码
1
7

说明

第一组数据中,按标准冒泡排序过程,恰好只会发生一次代价为 111 的交换,因此答案为 111。

第二组数据中,按照固定的冒泡排序顺序累计交换代价为777,因此答案为777。

思路和代码

本题主要是利用冒泡排序的交换路径唯一性, 从前往后扫描排序和每次从当前最大值开始进行交换排序本质上是同一个过程,只是观察角度不同。借助这个原理下面代码使用从当前最大值开始进行交换进行处理

  1. 假设当前已排序好的元素数量为x, 当前未排序中最大值maxValue的处于数组中位置为pos, 那么本次maxValue的交换路径为pos -> pos + 1, pos + 1 -> pos + 2.... , n-1-x-1 -> n-1-x

  2. 求上述1过程中交换路径的代价可以采用前缀和进行加速求解。使用优先队列维护当前最大的元素(值相同取位置更大的,保证稳定性),使用树状数组维护当前元素在"剩余数组"中的相对排名(求当前最大值)。

  3. 整体流程,重复执行以下操作

    • 取当前最大值
    • 用树状数组求它当前位置
    • 计算它经过的代价
    • 将其从树状数组和优先队列中删除。
  4. 累加所有代价和就是结果

算法时间复杂度为O(nlogn),空间复杂度为O(n)

cpp 复制代码
#include <iostream>
#include <vector>
#include <algorithm>
#include <functional>
#include <queue>
using namespace std;
using ll = long long;

const ll INF = 1e18;

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    int T;
    cin >> T;
    while (T--) {
        int n;
        cin >> n;
        vector<ll> a(n + 1);
        for (int i = 1; i <= n; ++i) cin >> a[i];
        vector<ll> b(n + 1);
        for (int i = 1; i <= n; ++i) cin >> b[i];
        
        // 边权 i交换是否计费
        vector<int> c(n, 0); 
        // 边权前缀和          
        vector<ll> pref_c(n, 0);       
        for (int i = 1; i < n; ++i) {
            c[i] = (b[i] < b[i+1]) ? 1 : 0;
            pref_c[i] = pref_c[i-1] + c[i];
        }
        
        // 最大值优先,值相同,位置大的优先
        priority_queue<pair<ll,int>> pq;
        
        for (int i = 1; i <= n; i++) {
            pq.push({a[i], i});
        }

        // 树状数组 维护当前元素相对位置
        vector<int> fenw(n + 2, 0);
        // 
        auto fenw_add = [&](int idx, int delta) {
            while (idx <= n) {
                fenw[idx] += delta;
                idx += idx & -idx;
            }
        };

        auto fenw_sum = [&](int idx) {
            int res = 0;
            while (idx > 0) {
                res += fenw[idx];
                idx -= idx & -idx;
            }
            return res;
        };

        for (int i = 1; i <= n; ++i) fenw_add(i, 1);
         
        ll ans = 0;
        int cur_len = n;
        for (int step = 0; step < n; ++step) {
            auto [val, pos] = pq.top();          // 当前最大值及其原始位置
            pq.pop();
            int rank = fenw_sum(pos);          // 在当前未排序数组中的排名
            // 贡献从 rank 到 cur_len-1 的边权总和
            ans += pref_c[cur_len - 1] - pref_c[rank - 1];
            // 删除该元素
            fenw_add(pos, -1);
            --cur_len;
        }
        cout << ans << '\n';
    }
    return 0;
}
相关推荐
To_OC8 小时前
LC 49 字母异位词分组:想到哈希表很简单,选对 key 才是精髓
javascript·算法·leetcode
用户9385156350713 小时前
从 O(n²) 到 O(nlogn):一文读懂快速排序的“快”与“妙”
javascript·算法
To_OC14 小时前
手写快排次次翻车?别死背快排模板了,这才是面试官想听的底层逻辑
javascript·算法·排序算法
饼干哥哥15 小时前
Reddit VOC调研太慢?搭一个AI专家团队半小时洞察任何品类|以猫用饮水机为例
人工智能·算法·ai编程
地平线开发者16 小时前
Transformer模型部署之性能优化指南
算法
地平线开发者17 小时前
人在途中:从“编译失败”到“模型可落地”——CUDA 自定义算子
算法·自动驾驶
半个落月20 小时前
从递归到快速排序:用 JavaScript 把分治思想讲明白
javascript·算法·面试
小月土星21 小时前
JavaScript 快速排序:从 pivot、双指针到分治思想
javascript·算法·面试
小月土星21 小时前
JavaScript 递归入门:从 1 到 n 求和,再到数组扁平化
javascript·算法·面试