2021牛客暑期多校训练营4 I题

I题: Inverse Pair

原题链接:https://ac.nowcoder.com/acm/contest/11255/I

题目大意

给定一个 1 1 1 n ( 1 ≤ n ≤ 2 × 1 0 5 ) n(1\le n\le 2×10^5) n(1n2×105) 的排序 a a a ,求序列 b ( b i ∈ { 0 , 1 } ) b(b_i\in \{0,1\}) b(bi{0,1}) ,使得序列 c ( c i = a i + b i ) c(c_i=a_i+b_i) c(ci=ai+bi) 的逆序数对数最少。

题解

初步分析

序列 b b b 的存在意义相当于对于排序 a a a 中的每个元素选择性执行一次 + 1 +1 +1 的操作。
因为排序 a a a 是排序(即 1 1 1 ~ n n n 中的每个数恰好出现一次),那么当我们执行 + 1 +1 +1 操作时,不难发现,当且仅当存在 a j = a i + 1 ( 1 ≤ j < i ≤ n ) a_j=a_i+1(1\le j< i\le n) aj=ai+1(1j<in) 时,操作才会使逆序数对减少 1 1 1 次(即逆序数对 ( j , i ) (j,i) (j,i) 消失),反之,操作不会直接对逆序数对的数量造成影响(后效性可能影响后面的决策从而间接造成影响)。

考虑后效性

若同时存在多对 a j = a i + 1 a_j=a_i+1 aj=ai+1 i , j i,j i,j 相互重合,则在操作过程中存在后效性。
我们设排序 a a a 的一个子序列为 D D D (大小为d),其满足 D i − 1 = D i + 1 ( 2 ≤ i ≤ d ) D_{i-1}=D_i+1(2\le i\le d) Di1=Di+1(2id) ,我们将其中 D 2 D_2 D2~ D d D_d Dd 是否 + 1 +1 +1 使得逆序数对的数量减少的状态用一个长度为 d − 1 d-1 d1 的01串表示。
那么显然,当 D i + 1 D_i+1 Di+1 时, D i D_i Di D i − 1 D_{i-1} Di1 所构成的逆序数对消失了,但是 D i + 1 D_{i+1} Di+1 D i D_i Di 的差值变为 2 2 2 ,即无法通过 D i + 1 + 1 D_{i+1}+1 Di+1+1来消去一组逆序数对。将这条规则转化到我们的01串上即是:任何一个1的下一位必须是0。
根据这条规则,我们的01串最优解应该是1010101…或0101010…的形式。
d − 1 d-1 d1 为奇数时,10101…0101比01010…1010更优(多消去一组逆序数对)
d − 1 d-1 d1 为偶数时,10101…1010与01010…0101都是最优解(都消去了 d − 1 2 \frac{d-1}{2} 2d1 组逆序数对)
显然,我们优先将左/右端点上的数 + 1 +1 +1 是一种恒可行的贪心策略,所以我们只需要有序(从小到大/从大到小)扫描排序 a a a 若遇到可以通过 + 1 +1 +1 消去逆序数对的情况,则优先消去先扫描到的。
参考一下案例:
排序 a = 4 , 3 , 2 , 1 a={4,3,2,1} a=4,3,2,1
若我们使 2 ( a 3 ) + 1 2(a_3)+1 2(a3)+1 ,则结果如下:
           c = 4 , 3 , 3 , 1 ( 仅 消 去 一 组 逆 序 数 对 , 此 时 修 改 a 2 / a 4 都 不 能 得 到 更 优 解 ) \ \ \ \ \ \ \ \ \ \ c={4,3,3,1}(仅消去一组逆序数对,此时修改a_2/a_4都不能得到更优解)           c=4,3,3,1(,a2/a4)

若我们使 3 ( a 2 ) + 1 3(a_2)+1 3(a2)+1 ,则结果如下:
           c = 4 , 4 , 2 , 1 ( 消 去 一 组 逆 序 数 对 , 此 时 修 改 a 4 能 得 到 更 优 解 如 下 ) \ \ \ \ \ \ \ \ \ \ c={4,4,2,1}(消去一组逆序数对,此时修改a_4能得到更优解如下)           c=4,4,2,1(,a4)

c = 4 , 4 , 2 , 2 ( 消 去 两 组 逆 序 数 对 , 此 时 为 最 优 解 )                  c={4,4,2,2}(消去两组逆序数对,此时为最优解)\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ c=4,4,2,2(,)                

注意事项

对于每个在前面出现过的 a i a_i ai 可以通过对其进行标记,方便后来扫描到 a i − 1 a_i-1 ai1 的值时查询是否需要 + 1 +1 +1 ,不必重新检查先前所有元素,优化时间复杂度。
对于最终求逆序对,采用归并排序优化复杂度至 O ( n    ⁣ l o g n ) O(n\ \!_{log}n) O(n logn)

参考代码

#include<bits/stdc++.h>
#define ll long long
using namespace std;
int n,a[200005],g[200005],p[200005];
ll merge_sort(int l,int r){
    if(l>=r)return 0;//若区间内元素少于2个则直接弹回
    int mid=(l+r)/2;
    ll cnt=0;
    cnt=merge_sort(l,mid)+merge_sort(mid+1,r);//先往下递归,保证两个子区间为已排序状态
    int i=l,j=mid+1,k=0;
    while(i<=mid&&j<=r){
        if(a[i]<=a[j])p[k++]=a[i++];//若前半部分中的值更小,将其放入暂存数组中
        else{
            cnt+=mid-i+1;//前半部分剩余元素都大于a[j],都可以组成逆序对
            p[k++]=a[j++];//若后半部分中的值更小,将其放入暂存数组中
        }
    }
    while(i<=mid)p[k++]=a[i++];//保证全部压入
    while(j<=r)p[k++]=a[j++];//保证全部压入
    for(i=l,j=0;i<=r;i++,j++)a[i]=p[j];//从暂存数组中转移
    return cnt;//返回值为该段[l,r]间的逆序对的数量
}
int main()
{
    std::ios::sync_with_stdio(false),cin.tie();
    cin>>n;
    for(int i=1;i<=n;i++)cin>>a[i];
    for(int i=1;i<=n;i++){
        if(g[a[i]+1])a[i]++;//若出现过a[i]+1,则将改元素+1
        else g[a[i]]=1;//若未出现过a[i]+1,则不+1并标记
    }
    cout<<merge_sort(1,n);//归并排序求逆序数对数量
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值