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(1≤n≤2×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(1≤j<i≤n) 时,操作才会使逆序数对减少
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)
Di−1=Di+1(2≤i≤d) ,我们将其中
D
2
D_2
D2~
D
d
D_d
Dd 是否
+
1
+1
+1 使得逆序数对的数量减少的状态用一个长度为
d
−
1
d-1
d−1 的01串表示。
那么显然,当
D
i
+
1
D_i+1
Di+1 时,
D
i
D_i
Di 与
D
i
−
1
D_{i-1}
Di−1 所构成的逆序数对消失了,但是
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
d−1 为奇数时,10101…0101比01010…1010更优(多消去一组逆序数对)
当
d
−
1
d-1
d−1 为偶数时,10101…1010与01010…0101都是最优解(都消去了
d
−
1
2
\frac{d-1}{2}
2d−1 组逆序数对)
显然,我们优先将左/右端点上的数
+
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
ai−1 的值时查询是否需要
+
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;
}