• 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;
    }
  • 相关阅读:
    Requests发送带cookies请求
    Python3 + requests + unittest接口测试
    「完结篇」网络爬虫+实时监控+推送微信
    「爬虫」从某网站爬取数据
    定时从某网站爬取压缩包
    如何转载文章...............
    数据库连接远程服务器报错
    记录常用的Java方法
    链接服务器 不同服务器查询,插入数据
    【学习笔记】树状数组
  • 原文地址:https://www.cnblogs.com/wgx666/p/12093865.html
Copyright © 2020-2023  润新知