zoukankan      html  css  js  c++  java
  • Codeforces Round #609 (div2) E. K Integers

    题目大意是 : 给你 1~n 的排列, 你每次都可以交换相邻位置的数, 问对于每一个i (i = 1, 2 ... n) 要得到有序的1~i的连续排列所需要交换的最少次数分别是多少?

    输入 : n ( n <= 2e5), 接下来一行n个数代表这个排列

    输出 : 输出n个数, 分别表示要得到连续有序的1~i (i = 1, 2 ... n) 的排列需要最少的交换次数

    样例输入

    5

    5 4 3 2 1

    样例输出:

    0 1 3 6 10

    想法 :

      这题我在尝试乱搞后果断放弃, 然后去研究那些红黑名大佬的代码, 研究了一个小时终于差不多看懂了 ( 留下了菜鸡的泪水- -  以下均为个人理解, 如有不对, 欢迎指正~

      首先我们考虑i = n 的情况, 使这个排列变成有序的排列所需要的最少交换次数 (也即冒泡排序的最小交换次数) 就是这个排列的逆序对数, 比如样例输入的5 4 3 2 1要有序则需要交换最少 1+2+3+4 = 10次. 而求解逆序对可以用树状数组, 而且树状数组可以把每一个1~i的排列的局部逆序对都求出来.

      对于i < n的情况, 就没那么好直接求了 比如2 4 1 5 3, 要求1~3连续有序的最少交换次数就还要把位于1~3之间的4和5也考虑进来, 但我们依然可以把它转化成上面那种i = n的情况来做: 说的直接点就是把位于 1~i 之间的大于 i 的数都给挤出去, 让 1~i 彼此相邻 ( 此时它们的相对顺序没变 ) , 然后就可以转成上述 i = n的情况了, 答案就是 [把位于 1~i 之间的大于 i 的数都给挤出去所需要的最少交换次数] + [1~i (还是原来的相对顺序)排列的逆序对的个数].

      就以2 4 1 5 3 为例, 要求1~3连续有序的最少交换次数. 首先按上面那个步骤来, 把4 和 5 挤出去, 但怎么挤出去交换次数最少呢? 我们很容易想到, 把2和3都往中间挤, 4 和 5被挤出去的交换次数最少, 也即2次, 此时排列变为 4 2 1 3 5, 然后只需要看2 1 3了,  这个排列逆序对为1, 所以1~3的连续有序的最少交换次数就是2+1 = 3 次. 所以整个过程描述起来就是, 对于每个 i , 求出排列1~i 的局部逆序对(也即不包含比i大的数的逆序对), 二分树状数组找到1~i 这个排列的中间位置( 同样是不包含比i大的数), 然后分别计算中间位置以及中间位置前的每个<= i 的数往中间位置挤所需要的交换次数和中间位置后每个<= i 的数往中间位置挤需要的交换次数, 这个怎么算呢? 可以用每个数和中间位置的 (位置差-1) 来表示需要的交换次数, 这里可以用另外一个树状数组, 记录每个 <=i 的位置前缀和来实现, 具体可以看代码.

    #include <bits/stdc++.h> 
    
    using namespace std;
    
    typedef long long ll;
    const int maxn = 2e5+10;
    
    int num[maxn];
    ll cnt_sum[maxn], pos_sum[maxn]; // sum(cnt_sum,i) 表示前i个位置中已经出现了多少个比当前数要小的数的个数
    int n;                // sum(pos_sum,i) 表示前i个位置中所有比当前数小的位置和
    
    inline lowbit(int x) { return x & -x;  }
    
    void add(ll* bit,int pos,int x) {
        while(pos <= n) {
            bit[pos] += x;
            pos += lowbit(pos);
        }
    }
    
    ll sum(const ll* bit,int pos) {
        ll ans = 0;
        while(pos > 0) {
            ans += bit[pos];
            pos -= lowbit(pos);
        }
        return ans;
    }
    
    int pos[maxn]; // 记录每个数的位置
    
    int main() {
        ios::sync_with_stdio(false);
        cin.tie(0);
        cin >> n;
        for(int i=1;i<=n;++i) {
            cin >> num[i];
            pos[num[i]] = i;
        }       
        ll inv = 0;
        for(int i=1;i<=n;++i) {
            inv += i-1-sum(cnt_sum,pos[i]); // 1~i这个排列的局部逆序对
            add(cnt_sum,pos[i],1);
            add(pos_sum,pos[i],pos[i]);
            int l = 1, r = n, mid;
            while(l < r) { // 二分找到那个上文说过的中间位置
                mid = l+r+1 >> 1;
                if(sum(cnt_sum,mid-1)*2 <= i)   l = mid;
                else r = mid-1;
            }
            mid = l;
            ll pre_cnt_sum = sum(cnt_sum,mid), pre_pos_sum = sum(pos_sum,mid); // 计算mid及mid左边的所有<=i的个数以及位置和
            ll mov = pre_cnt_sum * mid - pre_pos_sum - pre_cnt_sum*(pre_cnt_sum-1)/2; // mid左边所有<=i的数移往mid所需要的交换次数
            ll aft_cnt_sum = i - pre_cnt_sum; // mid右边.....同理. 这个求交换次数的过程手算模拟一下会更好理解一点, 文字不太好描述
            mov += sum(pos_sum,n) - pre_pos_sum - aft_cnt_sum * mid - aft_cnt_sum*(aft_cnt_sum+1)/2;
            cout << inv+mov << " 
    "[i == n];
        }
        return 0;
    }
  • 相关阅读:
    springcloud 项目源码 微服务 分布式 Activiti6 工作流 vue.js html 跨域 前后分离
    springcloud 项目源码 微服务 分布式 Activiti6 工作流 vue.js html 跨域 前后分离
    OA办公系统 Springboot Activiti6 工作流 集成代码生成器 vue.js 前后分离 跨域
    java企业官网源码 自适应响应式 freemarker 静态引擎 SSM 框架
    java OA办公系统源码 Springboot Activiti工作流 vue.js 前后分离 集成代码生成器
    springcloud 项目源码 微服务 分布式 Activiti6 工作流 vue.js html 跨域 前后分离
    java 视频播放 弹幕技术 视频弹幕 视频截图 springmvc mybatis SSM
    最后阶段总结
    第二阶段学习总结
    第一阶段学习总结
  • 原文地址:https://www.cnblogs.com/wgx666/p/12093865.html
Copyright © 2011-2022 走看看